1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.web.html;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.Reader;
25 import java.io.StringWriter;
26 import java.io.Writer;
27 import java.nio.charset.StandardCharsets;
28 import java.security.CodeSource;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Enumeration;
32 import java.util.List;
33 import java.util.zip.ZipEntry;
34 import java.util.zip.ZipFile;
35
36 import net.bull.javamelody.internal.common.HttpPart;
37 import net.bull.javamelody.internal.model.MavenArtifact;
38
39 /**
40  * Rapport html pour afficher un fichier source java.
41  * @author Emeric Vernat
42  */

43 class HtmlSourceReport extends HtmlAbstractReport {
44     private static final File JDK_SRC_FILE = getJdkSrcFile();
45     private static final List<String> TOMCAT_PACKAGES = Collections.unmodifiableList(
46             Arrays.asList("org.apache.tomcat""org.apache.catalina""org.apache.coyote",
47                     "org.apache.jasper""org.apache.el""org.apache.juli""org.apache.naming"));
48
49     private final String source;
50
51     HtmlSourceReport(String className, Writer writer) throws IOException {
52         super(writer);
53         this.source = getSource(normalizeClassName(className));
54     }
55
56     private static String normalizeClassName(String className) {
57         String temp = className;
58         while (temp.lastIndexOf('$') != -1) {
59             // className may be like org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$$EnhancerBySpringCGLIB$$e0b05818
60             temp = temp.substring(0, temp.lastIndexOf('$'));
61         }
62         if (temp.lastIndexOf('/') != -1) {
63             // for JDK 9+
64             temp = temp.substring(temp.lastIndexOf('/') + 1);
65         }
66         return temp;
67     }
68
69     private String getSource(String className) throws IOException {
70         final Class<?> clazz;
71         try {
72             clazz = Class.forName(className);
73         } catch (final ClassNotFoundException e) {
74             return null;
75         }
76
77         final CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
78         final String sourceFilePath = clazz.getName().replace('.', '/') + ".java";
79         if (clazz.getName().startsWith("java.")
80                 || clazz.getName().startsWith("javax.") && codeSource == null) {
81             if (JDK_SRC_FILE != null) {
82                 return getSourceFromZip(sourceFilePath, JDK_SRC_FILE);
83             }
84         } else if (codeSource != null) {
85             final File sourceJarFile = MavenArtifact.getSourceJarFile(codeSource.getLocation());
86             if (sourceJarFile != null) {
87                 return getSourceFromZip(sourceFilePath, sourceJarFile);
88             }
89         }
90         if (clazz.getName().startsWith("org.apache.")) {
91             for (final String tomcatPackage : TOMCAT_PACKAGES) {
92                 if (clazz.getName().startsWith(tomcatPackage + '.')) {
93                     final File tomcatSrcFile = MavenArtifact.getTomcatSrcZipFile();
94                     if (tomcatSrcFile != null) {
95                         assert tomcatSrcFile.getName().endsWith(".zip");
96                         final String entryName = tomcatSrcFile.getName().substring(0,
97                                 tomcatSrcFile.getName().length() - ".zip".length()) + "/java/"
98                                 + sourceFilePath;
99                         return getSourceFromZip(entryName, tomcatSrcFile);
100                     }
101                 }
102             }
103         }
104         return null;
105     }
106
107     private static File getJdkSrcFile() {
108         File file = new File(System.getProperty("java.home"));
109         if ("jre".equalsIgnoreCase(file.getName())) {
110             file = file.getParentFile();
111         }
112         File srcZipFile = new File(file, "src.zip");
113         if (srcZipFile.exists()) {
114             return srcZipFile;
115         }
116         // for JDK 9 +
117         srcZipFile = new File(file, "lib/src.zip");
118         if (srcZipFile.exists()) {
119             return srcZipFile;
120         }
121         return null;
122     }
123
124     private String getSourceFromZip(String entryName, File srcJarFile) throws IOException {
125         try (ZipFile zipFile = new ZipFile(srcJarFile)) {
126             ZipEntry entry = zipFile.getEntry(entryName);
127             if (entry == null) {
128                 // for JDK 9 + and maybe some others
129                 final Enumeration<? extends ZipEntry> entries = zipFile.entries();
130                 while (entries.hasMoreElements()) {
131                     entry = entries.nextElement();
132                     if (entry.getName().endsWith(entryName)) {
133                         break;
134                     }
135                 }
136                 if (!entry.getName().endsWith(entryName)) {
137                     return null;
138                 }
139             }
140             final StringWriter writer = new StringWriter();
141             try (InputStream inputStream = zipFile.getInputStream(entry)) {
142                 try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
143                     final char[] chars = new char[1024];
144                     int read = reader.read(chars);
145                     while (read != -1) {
146                         writer.write(chars, 0, read);
147                         read = reader.read(chars);
148                     }
149                 }
150             }
151             return writer.toString();
152         }
153     }
154
155     @Override
156     void toHtml() throws IOException {
157         if (source != null) {
158             String html = JavaHTMLizer.htmlize(source);
159             html = JavaHTMLizer.addLineNumbers(html);
160             writeDirectly("<code>");
161             writeDirectly(html);
162             writeDirectly("</code>");
163         } else {
164             write("#source_not_found#");
165         }
166     }
167
168     static String htmlEncodeStackTraceElement(String element) {
169         if (element.endsWith(")") && !element.endsWith("(Native Method)")
170                 && !element.endsWith("(Unknown Source)")) {
171             final int index3 = element.lastIndexOf(':');
172             final int index2 = element.lastIndexOf('(');
173             final int index1 = element.lastIndexOf('.', index2);
174             final int index0 = element.lastIndexOf(' ', index1);
175             if (index1 > index0 && index2 != -1 && index3 > index2) {
176                 final String classNameEncoded = urlEncode(element.substring(index0 + 1, index1));
177                 return htmlEncodeButNotSpace(element.substring(0, index2 + 1)) + "<a href='?part="
178                         + HttpPart.SOURCE + "&amp;class=" + classNameEncoded + '#'
179                         + urlEncode(element.substring(index3 + 1, element.length() - 1))
180                         + "' class='lightwindow' type='external' title='" + classNameEncoded + "'>"
181                         + htmlEncode(element.substring(index2 + 1, element.length() - 1)) + "</a>)";
182             }
183         }
184         return htmlEncodeButNotSpace(element);
185     }
186
187     static String htmlEncodeStackTraceElementAndTabs(String element) {
188         return htmlEncodeStackTraceElement(element).replace("\t",
189                 "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
190     }
191
192     static String addLinkToClassName(String className) {
193         String cleanClassName = className;
194         if (cleanClassName.endsWith("[]")) {
195             cleanClassName = cleanClassName.substring(0, cleanClassName.length() - 2);
196         }
197         return "<a href='?part=" + HttpPart.SOURCE + "&amp;class=" + cleanClassName
198                 + "' class='lightwindow' type='external' title='" + cleanClassName + "'>"
199                 + className + "</a>";
200     }
201 }
202