001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.tools.sourceformatter;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
018    import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
019    import com.liferay.portal.kernel.util.CharPool;
020    import com.liferay.portal.kernel.util.GetterUtil;
021    import com.liferay.portal.kernel.util.StringBundler;
022    import com.liferay.portal.kernel.util.StringPool;
023    import com.liferay.portal.kernel.util.StringUtil;
024    import com.liferay.portal.kernel.util.Validator;
025    
026    import java.io.File;
027    import java.io.IOException;
028    
029    import java.util.ArrayList;
030    import java.util.HashMap;
031    import java.util.HashSet;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Set;
035    import java.util.regex.Matcher;
036    import java.util.regex.Pattern;
037    
038    /**
039     * @author Hugo Huijser
040     */
041    public class JSPSourceProcessor extends BaseSourceProcessor {
042    
043            protected void addImportCounts(String content) {
044                    Matcher matcher = _importsPattern.matcher(content);
045    
046                    while (matcher.find()) {
047                            String importName = matcher.group(1);
048    
049                            int count = 0;
050    
051                            if (_importCountMap.containsKey(importName)) {
052                                    count = _importCountMap.get(importName);
053                            }
054                            else {
055                                    int pos = importName.lastIndexOf(StringPool.PERIOD);
056    
057                                    String importClassName = importName.substring(pos + 1);
058    
059                                    if (_importClassNames.contains(importClassName)) {
060                                            _duplicateImportClassNames.add(importClassName);
061                                    }
062                                    else {
063                                            _importClassNames.add(importClassName);
064                                    }
065                            }
066    
067                            _importCountMap.put(importName, count + 1);
068                    }
069            }
070    
071            protected void addJSPIncludeFileNames(String fileName) {
072                    String content = _jspContents.get(fileName);
073    
074                    if (Validator.isNull(content)) {
075                            return;
076                    }
077    
078                    for (int x = 0;;) {
079                            x = content.indexOf("<%@ include file=", x);
080    
081                            if (x == -1) {
082                                    break;
083                            }
084    
085                            x = content.indexOf(StringPool.QUOTE, x);
086    
087                            if (x == -1) {
088                                    break;
089                            }
090    
091                            int y = content.indexOf(StringPool.QUOTE, x + 1);
092    
093                            if (y == -1) {
094                                    break;
095                            }
096    
097                            String includeFileName = content.substring(x + 1, y);
098    
099                            Matcher matcher = _jspIncludeFilePattern.matcher(includeFileName);
100    
101                            if (!matcher.find()) {
102                                    throw new RuntimeException(
103                                            "Invalid include " + includeFileName);
104                            }
105    
106                            includeFileName = buildFullPathIncludeFileName(
107                                    fileName, includeFileName);
108    
109                            if ((includeFileName.endsWith("jsp") ||
110                                     includeFileName.endsWith("jspf")) &&
111                                    !includeFileName.endsWith("html/common/init.jsp") &&
112                                    !includeFileName.endsWith("html/portlet/init.jsp") &&
113                                    !includeFileName.endsWith("html/taglib/init.jsp") &&
114                                    !_includeFileNames.contains(includeFileName)) {
115    
116                                    _includeFileNames.add(includeFileName);
117                            }
118    
119                            x = y;
120                    }
121            }
122    
123            protected void addJSPReferenceFileNames(String fileName) {
124                    for (Map.Entry<String, String> entry : _jspContents.entrySet()) {
125                            String referenceFileName = entry.getKey();
126    
127                            if (_includeFileNames.contains(referenceFileName)) {
128                                    continue;
129                            }
130    
131                            String sharedPath = fileName.substring(
132                                    0, StringUtil.startsWithWeight(referenceFileName, fileName));
133    
134                            if (Validator.isNull(sharedPath) ||
135                                    !sharedPath.contains(StringPool.SLASH)) {
136    
137                                    continue;
138                            }
139    
140                            if (!sharedPath.endsWith(StringPool.SLASH)) {
141                                    sharedPath = sharedPath.substring(
142                                            0, sharedPath.lastIndexOf(CharPool.SLASH) + 1);
143                            }
144    
145                            String content = null;
146    
147                            for (int x = -1;;) {
148                                    x = sharedPath.indexOf(CharPool.SLASH, x + 1);
149    
150                                    if (x == -1) {
151                                            break;
152                                    }
153    
154                                    if (content == null) {
155                                            content = entry.getValue();
156                                    }
157    
158                                    if (content.contains(
159                                                    "<%@ include file=\"" + fileName.substring(x))) {
160    
161                                            _includeFileNames.add(referenceFileName);
162    
163                                            break;
164                                    }
165                            }
166                    }
167            }
168    
169            protected void addJSPUnusedImports(
170                    String fileName, List<String> importLines,
171                    List<String> unneededImports) {
172    
173                    for (String importLine : importLines) {
174                            int x = importLine.indexOf(StringPool.QUOTE);
175                            int y = importLine.indexOf(StringPool.QUOTE, x + 1);
176    
177                            if ((x == -1) || (y == -1)) {
178                                    continue;
179                            }
180    
181                            String className = importLine.substring(x + 1, y);
182    
183                            className = className.substring(
184                                    className.lastIndexOf(StringPool.PERIOD) + 1);
185    
186                            String regex = "[^A-Za-z0-9_\"]" + className + "[^A-Za-z0-9_\"]";
187    
188                            if (hasUnusedJSPTerm(fileName, regex, "class")) {
189                                    unneededImports.add(importLine);
190                            }
191                    }
192            }
193    
194            protected String buildFullPathIncludeFileName(
195                    String fileName, String includeFileName) {
196    
197                    String topLevelDirName = null;
198    
199                    int x = includeFileName.indexOf(CharPool.SLASH, 1);
200    
201                    if (x != -1) {
202                            topLevelDirName = includeFileName.substring(1, x);
203                    }
204    
205                    String path = fileName;
206    
207                    while (true) {
208                            int y = path.lastIndexOf(CharPool.SLASH);
209    
210                            if (y == -1) {
211                                    return StringPool.BLANK;
212                            }
213    
214                            if (Validator.isNull(topLevelDirName) ||
215                                    path.equals(topLevelDirName) ||
216                                    path.endsWith(StringPool.SLASH + topLevelDirName)) {
217    
218                                    String fullPathIncludeFileName =
219                                            path.substring(0, y) + includeFileName;
220    
221                                    if (_jspContents.containsKey(fullPathIncludeFileName) &&
222                                            !fullPathIncludeFileName.equals(fileName)) {
223    
224                                            return fullPathIncludeFileName;
225                                    }
226                            }
227    
228                            path = path.substring(0, y);
229                    }
230            }
231    
232            protected boolean checkTaglibVulnerability(
233                    String jspContent, String vulnerability) {
234    
235                    int pos1 = -1;
236    
237                    do {
238                            pos1 = jspContent.indexOf(vulnerability, pos1 + 1);
239    
240                            if (pos1 != -1) {
241                                    int pos2 = jspContent.lastIndexOf(CharPool.LESS_THAN, pos1);
242    
243                                    while ((pos2 > 0) &&
244                                               (jspContent.charAt(pos2 + 1) == CharPool.PERCENT)) {
245    
246                                            pos2 = jspContent.lastIndexOf(CharPool.LESS_THAN, pos2 - 1);
247                                    }
248    
249                                    String tagContent = jspContent.substring(pos2, pos1);
250    
251                                    if (!tagContent.startsWith("<aui:") &&
252                                            !tagContent.startsWith("<liferay-portlet:") &&
253                                            !tagContent.startsWith("<liferay-util:") &&
254                                            !tagContent.startsWith("<portlet:")) {
255    
256                                            return true;
257                                    }
258                            }
259                    }
260                    while (pos1 != -1);
261    
262                    return false;
263            }
264    
265            protected void checkXSS(String fileName, String jspContent) {
266                    Matcher matcher = _xssPattern.matcher(jspContent);
267    
268                    while (matcher.find()) {
269                            boolean xssVulnerable = false;
270    
271                            String jspVariable = matcher.group(1);
272    
273                            String anchorVulnerability = " href=\"<%= " + jspVariable + " %>";
274    
275                            if (checkTaglibVulnerability(jspContent, anchorVulnerability)) {
276                                    xssVulnerable = true;
277                            }
278    
279                            String inputVulnerability = " value=\"<%= " + jspVariable + " %>";
280    
281                            if (checkTaglibVulnerability(jspContent, inputVulnerability)) {
282                                    xssVulnerable = true;
283                            }
284    
285                            String inlineStringVulnerability1 = "'<%= " + jspVariable + " %>";
286    
287                            if (jspContent.contains(inlineStringVulnerability1)) {
288                                    xssVulnerable = true;
289                            }
290    
291                            String inlineStringVulnerability2 = "(\"<%= " + jspVariable + " %>";
292    
293                            if (jspContent.contains(inlineStringVulnerability2)) {
294                                    xssVulnerable = true;
295                            }
296    
297                            String inlineStringVulnerability3 = " \"<%= " + jspVariable + " %>";
298    
299                            if (jspContent.contains(inlineStringVulnerability3)) {
300                                    xssVulnerable = true;
301                            }
302    
303                            String documentIdVulnerability = ".<%= " + jspVariable + " %>";
304    
305                            if (jspContent.contains(documentIdVulnerability)) {
306                                    xssVulnerable = true;
307                            }
308    
309                            if (xssVulnerable) {
310                                    processErrorMessage(
311                                            fileName, "(xss): " + fileName + " (" + jspVariable + ")");
312                            }
313                    }
314            }
315    
316            @Override
317            protected String doFormat(
318                            File file, String fileName, String absolutePath, String content)
319                    throws Exception {
320    
321                    String newContent = formatJSP(fileName, absolutePath, content);
322    
323                    newContent = StringUtil.replace(
324                            newContent,
325                            new String[] {
326                                    "<br/>", "\"/>", "\" >", "@page import", "\"%>", ")%>", "else{",
327                                    "for(", "function (", "if(", "javascript: ", "while(", "){\n",
328                                    ";;\n", "\n\n\n"
329                            },
330                            new String[] {
331                                    "<br />", "\" />", "\">", "@ page import", "\" %>", ") %>",
332                                    "else {", "for (", "function(", "if (", "javascript:",
333                                    "while (", ") {\n", ";\n", "\n\n"
334                            });
335    
336                    newContent = fixCompatClassImports(absolutePath, newContent);
337    
338                    if (_stripJSPImports && !_jspContents.isEmpty()) {
339                            try {
340                                    newContent = stripJSPImports(fileName, newContent);
341                            }
342                            catch (RuntimeException re) {
343                                    _stripJSPImports = false;
344                            }
345                    }
346    
347                    if (portalSource &&
348                            content.contains("page import=") &&
349                            !fileName.contains("init.jsp") &&
350                            !fileName.contains("init-ext.jsp") &&
351                            !fileName.contains("/taglib/aui/") &&
352                            !fileName.endsWith("touch.jsp") &&
353                            (fileName.endsWith(".jspf") || content.contains("include file="))) {
354    
355                            processErrorMessage(
356                                    fileName, "move imports to init.jsp: " + fileName);
357                    }
358    
359                    newContent = fixCopyright(newContent, absolutePath, fileName);
360    
361                    newContent = StringUtil.replace(
362                            newContent,
363                            new String[] {
364                                    "alert('<%= LanguageUtil.", "alert(\"<%= LanguageUtil.",
365                                    "confirm('<%= LanguageUtil.", "confirm(\"<%= LanguageUtil."
366                            },
367                            new String[] {
368                                    "alert('<%= UnicodeLanguageUtil.",
369                                    "alert(\"<%= UnicodeLanguageUtil.",
370                                    "confirm('<%= UnicodeLanguageUtil.",
371                                    "confirm(\"<%= UnicodeLanguageUtil."
372                            });
373    
374                    if (newContent.contains("    ")) {
375                            if (!fileName.matches(".*template.*\\.vm$")) {
376                                    processErrorMessage(fileName, "tab: " + fileName);
377                            }
378                    }
379    
380                    if (fileName.endsWith("init.jsp") || fileName.endsWith("init.jspf")) {
381                            int x = newContent.indexOf("<%@ page import=");
382    
383                            int y = newContent.lastIndexOf("<%@ page import=");
384    
385                            y = newContent.indexOf("%>", y);
386    
387                            if ((x != -1) && (y != -1) && (y > x)) {
388    
389                                    // Set compressImports to false to decompress imports
390    
391                                    boolean compressImports = true;
392    
393                                    if (compressImports) {
394                                            String imports = newContent.substring(x, y);
395    
396                                            imports = StringUtil.replace(
397                                                    imports, new String[] {"%>\r\n<%@ ", "%>\n<%@ "},
398                                                    new String[] {"%><%@\r\n", "%><%@\n"});
399    
400                                            newContent =
401                                                    newContent.substring(0, x) + imports +
402                                                            newContent.substring(y);
403                                    }
404                            }
405                    }
406    
407                    newContent = fixSessionKey(fileName, newContent, sessionKeyPattern);
408                    newContent = fixSessionKey(
409                            fileName, newContent, taglibSessionKeyPattern);
410    
411                    checkLanguageKeys(fileName, newContent, languageKeyPattern);
412                    checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern1);
413                    checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern2);
414                    checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern3);
415    
416                    checkXSS(fileName, newContent);
417    
418                    // LPS-47682
419    
420                    newContent = fixIncorrectParameterTypeForLanguageUtil(
421                            newContent, true, fileName);
422    
423                    Matcher matcher = _javaClassPattern.matcher(newContent);
424    
425                    if (matcher.find()) {
426                            String javaClassContent = matcher.group();
427    
428                            javaClassContent = javaClassContent.substring(1);
429    
430                            String beforeJavaClass = newContent.substring(
431                                    0, matcher.start() + 1);
432    
433                            int javaClassLineCount =
434                                    StringUtil.count(beforeJavaClass, "\n") + 1;
435    
436                            newContent = formatJavaTerms(
437                                    fileName, absolutePath, newContent, javaClassContent,
438                                    javaClassLineCount, null, null, null);
439                    }
440    
441                    if (!content.equals(newContent)) {
442                            _jspContents.put(fileName, newContent);
443                    }
444    
445                    return newContent;
446            }
447    
448            @Override
449            protected void format() throws Exception {
450                    _moveFrequentlyUsedImportsToCommonInit = GetterUtil.getBoolean(
451                            getProperty("move.frequently.used.imports.to.common.init"));
452                    _unusedVariablesExclusions = getPropertyList(
453                            "jsp.unused.variables.excludes.files");
454    
455                    String[] excludes = new String[] {"**\\null.jsp", "**\\tools\\**"};
456                    String[] includes = new String[] {
457                            "**\\*.jsp", "**\\*.jspf", "**\\*.vm"
458                    };
459    
460                    List<String> fileNames = getFileNames(excludes, includes);
461    
462                    Pattern pattern = Pattern.compile(
463                            "\\s*@\\s*include\\s*file=['\"](.*)['\"]");
464    
465                    for (String fileName : fileNames) {
466                            File file = new File(BASEDIR + fileName);
467    
468                            fileName = StringUtil.replace(
469                                    fileName, StringPool.BACK_SLASH, StringPool.SLASH);
470    
471                            String absolutePath = getAbsolutePath(file);
472    
473                            String content = fileUtil.read(file);
474    
475                            Matcher matcher = pattern.matcher(content);
476    
477                            String newContent = content;
478    
479                            while (matcher.find()) {
480                                    newContent = StringUtil.replaceFirst(
481                                            newContent, matcher.group(),
482                                            "@ include file=\"" + matcher.group(1) + "\"",
483                                            matcher.start());
484                            }
485    
486                            processFormattedFile(file, fileName, content, newContent);
487    
488                            if (portalSource &&
489                                    _moveFrequentlyUsedImportsToCommonInit &&
490                                    fileName.endsWith("/init.jsp") &&
491                                    !absolutePath.contains("/modules/") &&
492                                    !fileName.endsWith("/common/init.jsp")) {
493    
494                                    addImportCounts(content);
495                            }
496    
497                            _jspContents.put(fileName, newContent);
498                    }
499    
500                    if (portalSource && _moveFrequentlyUsedImportsToCommonInit) {
501                            moveFrequentlyUsedImportsToCommonInit(4);
502                    }
503    
504                    for (String fileName : fileNames) {
505                            format(fileName);
506                    }
507            }
508    
509            protected String formatJSP(
510                            String fileName, String absolutePath, String content)
511                    throws IOException {
512    
513                    StringBundler sb = new StringBundler();
514    
515                    String currentAttributeAndValue = null;
516                    String previousAttribute = null;
517                    String previousAttributeAndValue = null;
518    
519                    String currentException = null;
520                    String previousException = null;
521    
522                    boolean hasUnsortedExceptions = false;
523    
524                    try (UnsyncBufferedReader unsyncBufferedReader =
525                                    new UnsyncBufferedReader(new UnsyncStringReader(content))) {
526    
527                            _checkedForIncludesFileNames = new HashSet<String>();
528                            _includeFileNames = new HashSet<String>();
529    
530                            int lineCount = 0;
531    
532                            String line = null;
533    
534                            String previousLine = StringPool.BLANK;
535    
536                            boolean readAttributes = false;
537    
538                            boolean javaSource = false;
539    
540                            while ((line = unsyncBufferedReader.readLine()) != null) {
541                                    lineCount++;
542    
543                                    if (portalSource && hasUnusedTaglib(fileName, line)) {
544                                            continue;
545                                    }
546    
547                                    if (!fileName.contains("jsonw") ||
548                                            !fileName.endsWith("action.jsp")) {
549    
550                                            line = trimLine(line, false);
551                                    }
552    
553                                    if (line.contains("<aui:button ") &&
554                                            line.contains("type=\"button\"")) {
555    
556                                            processErrorMessage(
557                                                    fileName, "aui:button " + fileName + " " + lineCount);
558                                    }
559    
560                                    if (line.contains("debugger.")) {
561                                            processErrorMessage(
562                                                    fileName, "debugger " + fileName + " " + lineCount);
563                                    }
564    
565                                    String trimmedLine = StringUtil.trimLeading(line);
566                                    String trimmedPreviousLine = StringUtil.trimLeading(
567                                            previousLine);
568    
569                                    checkStringBundler(trimmedLine, fileName, lineCount);
570    
571                                    checkEmptyCollection(trimmedLine, fileName, lineCount);
572    
573                                    if (trimmedLine.equals("<%") || trimmedLine.equals("<%!")) {
574                                            javaSource = true;
575                                    }
576                                    else if (trimmedLine.equals("%>")) {
577                                            javaSource = false;
578                                    }
579    
580                                    if (javaSource || trimmedLine.contains("<%= ")) {
581                                            checkInefficientStringMethods(
582                                                    line, fileName, absolutePath, lineCount);
583                                    }
584    
585                                    if (javaSource && portalSource &&
586                                            !isExcluded(
587                                                    _unusedVariablesExclusions, absolutePath, lineCount) &&
588                                            !_jspContents.isEmpty() &&
589                                            hasUnusedVariable(fileName, trimmedLine)) {
590    
591                                            continue;
592                                    }
593    
594                                    // LPS-47179
595    
596                                    if (line.contains(".sendRedirect(") &&
597                                            !fileName.endsWith("_jsp.jsp")) {
598    
599                                            processErrorMessage(
600                                                    fileName,
601                                                    "Do not use sendRedirect in jsp: " + fileName + " " +
602                                                            lineCount);
603                                    }
604    
605                                    if (!trimmedLine.equals("%>") && line.contains("%>") &&
606                                            !line.contains("--%>") && !line.contains(" %>")) {
607    
608                                            line = StringUtil.replace(line, "%>", " %>");
609                                    }
610    
611                                    if (line.contains("<%=") && !line.contains("<%= ")) {
612                                            line = StringUtil.replace(line, "<%=", "<%= ");
613                                    }
614    
615                                    if (trimmedPreviousLine.equals("%>") &&
616                                            Validator.isNotNull(line) && !trimmedLine.equals("-->")) {
617    
618                                            sb.append("\n");
619                                    }
620                                    else if (Validator.isNotNull(previousLine) &&
621                                                     !trimmedPreviousLine.equals("<!--") &&
622                                                     trimmedLine.equals("<%")) {
623    
624                                            sb.append("\n");
625                                    }
626                                    else if (trimmedPreviousLine.equals("<%") &&
627                                                     Validator.isNull(line)) {
628    
629                                            continue;
630                                    }
631                                    else if (trimmedPreviousLine.equals("<%") &&
632                                                     trimmedLine.startsWith("//")) {
633    
634                                            sb.append("\n");
635                                    }
636                                    else if (Validator.isNull(previousLine) &&
637                                                     trimmedLine.equals("%>") && (sb.index() > 2)) {
638    
639                                            String lineBeforePreviousLine = sb.stringAt(sb.index() - 3);
640    
641                                            if (!lineBeforePreviousLine.startsWith("//")) {
642                                                    sb.setIndex(sb.index() - 1);
643                                            }
644                                    }
645    
646                                    if ((trimmedLine.startsWith("if (") ||
647                                             trimmedLine.startsWith("else if (") ||
648                                             trimmedLine.startsWith("while (")) &&
649                                            trimmedLine.endsWith(") {")) {
650    
651                                            checkIfClauseParentheses(trimmedLine, fileName, lineCount);
652                                    }
653    
654                                    if (readAttributes) {
655                                            if (!trimmedLine.startsWith(StringPool.FORWARD_SLASH) &&
656                                                    !trimmedLine.startsWith(StringPool.GREATER_THAN)) {
657    
658                                                    int pos = trimmedLine.indexOf(StringPool.EQUAL);
659    
660                                                    if (pos != -1) {
661                                                            String attribute = trimmedLine.substring(0, pos);
662    
663                                                            if (!trimmedLine.endsWith(StringPool.APOSTROPHE) &&
664                                                                    !trimmedLine.endsWith(
665                                                                            StringPool.GREATER_THAN) &&
666                                                                    !trimmedLine.endsWith(StringPool.QUOTE)) {
667    
668                                                                    processErrorMessage(
669                                                                            fileName,
670                                                                            "attribute: " + fileName + " " + lineCount);
671    
672                                                                    readAttributes = false;
673                                                            }
674                                                            else if (trimmedLine.endsWith(
675                                                                                    StringPool.APOSTROPHE) &&
676                                                                             !trimmedLine.contains(StringPool.QUOTE)) {
677    
678                                                                    line = StringUtil.replace(
679                                                                            line, StringPool.APOSTROPHE,
680                                                                                    StringPool.QUOTE);
681    
682                                                                    readAttributes = false;
683                                                            }
684                                                            else if (Validator.isNotNull(previousAttribute)) {
685                                                                    if (!isAttributName(attribute) &&
686                                                                            !attribute.startsWith(
687                                                                                    StringPool.LESS_THAN)) {
688    
689                                                                            processErrorMessage(
690                                                                                    fileName,
691                                                                                    "attribute: " + fileName + " " +
692                                                                                            lineCount);
693    
694                                                                            readAttributes = false;
695                                                                    }
696                                                                    else if (Validator.isNull(
697                                                                                            previousAttributeAndValue) &&
698                                                                                     (previousAttribute.compareTo(
699                                                                                             attribute) > 0)) {
700    
701                                                                            previousAttributeAndValue = previousLine;
702                                                                            currentAttributeAndValue = line;
703                                                                    }
704                                                            }
705    
706                                                            if (!readAttributes) {
707                                                                    previousAttribute = null;
708                                                                    previousAttributeAndValue = null;
709                                                            }
710                                                            else {
711                                                                    previousAttribute = attribute;
712                                                            }
713                                                    }
714                                            }
715                                            else {
716                                                    previousAttribute = null;
717    
718                                                    readAttributes = false;
719                                            }
720                                    }
721    
722                                    if (!hasUnsortedExceptions) {
723                                            int x = line.indexOf("<liferay-ui:error exception=\"<%=");
724    
725                                            if (x != -1) {
726                                                    int y = line.indexOf(".class %>", x);
727    
728                                                    if (y != -1) {
729                                                            currentException = line.substring(x, y);
730    
731                                                            if (Validator.isNotNull(previousException) &&
732                                                                    (previousException.compareTo(currentException) >
733                                                                            0)) {
734    
735                                                                    currentException = line;
736                                                                    previousException = previousLine;
737    
738                                                                    hasUnsortedExceptions = true;
739                                                            }
740                                                    }
741                                            }
742    
743                                            if (!hasUnsortedExceptions) {
744                                                    previousException = currentException;
745                                                    currentException = null;
746                                            }
747                                    }
748    
749                                    if (trimmedLine.startsWith(StringPool.LESS_THAN) &&
750                                            !trimmedLine.startsWith("<%") &&
751                                            !trimmedLine.startsWith("<!")) {
752    
753                                            if (!trimmedLine.contains(StringPool.GREATER_THAN) &&
754                                                    !trimmedLine.contains(StringPool.SPACE)) {
755    
756                                                    readAttributes = true;
757                                            }
758                                            else {
759                                                    line = sortAttributes(fileName, line, lineCount, true);
760                                            }
761                                    }
762    
763                                    if (!trimmedLine.contains(StringPool.DOUBLE_SLASH) &&
764                                            !trimmedLine.startsWith(StringPool.STAR)) {
765    
766                                            while (trimmedLine.contains(StringPool.TAB)) {
767                                                    line = StringUtil.replaceLast(
768                                                            line, StringPool.TAB, StringPool.SPACE);
769    
770                                                    trimmedLine = StringUtil.replaceLast(
771                                                            trimmedLine, StringPool.TAB, StringPool.SPACE);
772                                            }
773    
774                                            while (trimmedLine.contains(StringPool.DOUBLE_SPACE) &&
775                                                       !trimmedLine.contains(
776                                                               StringPool.QUOTE + StringPool.DOUBLE_SPACE) &&
777                                                       !fileName.endsWith(".vm")) {
778    
779                                                    line = StringUtil.replaceLast(
780                                                            line, StringPool.DOUBLE_SPACE, StringPool.SPACE);
781    
782                                                    trimmedLine = StringUtil.replaceLast(
783                                                            trimmedLine, StringPool.DOUBLE_SPACE,
784                                                            StringPool.SPACE);
785                                            }
786                                    }
787    
788                                    if (!fileName.endsWith("/touch.jsp")) {
789                                            int x = line.indexOf("<%@ include file");
790    
791                                            if (x != -1) {
792                                                    x = line.indexOf(StringPool.QUOTE, x);
793    
794                                                    int y = line.indexOf(StringPool.QUOTE, x + 1);
795    
796                                                    if (y != -1) {
797                                                            String includeFileName = line.substring(x + 1, y);
798    
799                                                            Matcher matcher = _jspIncludeFilePattern.matcher(
800                                                                    includeFileName);
801    
802                                                            if (!matcher.find()) {
803                                                                    processErrorMessage(
804                                                                            fileName,
805                                                                            "include: " + fileName + " " + lineCount);
806                                                            }
807                                                    }
808                                            }
809                                    }
810    
811                                    line = replacePrimitiveWrapperInstantiation(
812                                            fileName, line, lineCount);
813    
814                                    previousLine = line;
815    
816                                    sb.append(line);
817                                    sb.append("\n");
818                            }
819                    }
820    
821                    content = sb.toString();
822    
823                    if (content.endsWith("\n")) {
824                            content = content.substring(0, content.length() - 1);
825                    }
826    
827                    content = formatTaglibQuotes(fileName, content, StringPool.QUOTE);
828                    content = formatTaglibQuotes(fileName, content, StringPool.APOSTROPHE);
829    
830                    if (Validator.isNotNull(previousAttributeAndValue)) {
831                            content = StringUtil.replaceFirst(
832                                    content,
833                                    previousAttributeAndValue + "\n" + currentAttributeAndValue,
834                                    currentAttributeAndValue + "\n" + previousAttributeAndValue);
835                    }
836    
837                    if (hasUnsortedExceptions) {
838                            if ((StringUtil.count(content, currentException) > 1) ||
839                                    (StringUtil.count(content, previousException) > 1)) {
840    
841                                    processErrorMessage(
842                                            fileName, "unsorted exceptions: " + fileName);
843                            }
844                            else {
845                                    content = StringUtil.replaceFirst(
846                                            content, previousException, currentException);
847    
848                                    content = StringUtil.replaceLast(
849                                            content, currentException, previousException);
850                            }
851                    }
852    
853                    return content;
854            }
855    
856            protected String formatTaglibQuotes(
857                    String fileName, String content, String quoteType) {
858    
859                    String quoteFix = StringPool.APOSTROPHE;
860    
861                    if (quoteFix.equals(quoteType)) {
862                            quoteFix = StringPool.QUOTE;
863                    }
864    
865                    Pattern pattern = Pattern.compile(getTaglibRegex(quoteType));
866    
867                    Matcher matcher = pattern.matcher(content);
868    
869                    while (matcher.find()) {
870                            int x = content.indexOf(quoteType + "<%=", matcher.start());
871                            int y = content.indexOf("%>" + quoteType, x);
872    
873                            while ((x != -1) && (y != -1)) {
874                                    String beforeResult = content.substring(matcher.start(), x);
875    
876                                    if (beforeResult.contains(" />\"")) {
877                                            break;
878                                    }
879    
880                                    String result = content.substring(x + 1, y + 2);
881    
882                                    if (result.contains(quoteType)) {
883                                            int lineCount = 1;
884    
885                                            char[] contentCharArray = content.toCharArray();
886    
887                                            for (int i = 0; i < x; i++) {
888                                                    if (contentCharArray[i] == CharPool.NEW_LINE) {
889                                                            lineCount++;
890                                                    }
891                                            }
892    
893                                            if (!result.contains(quoteFix)) {
894                                                    StringBundler sb = new StringBundler(5);
895    
896                                                    sb.append(content.substring(0, x));
897                                                    sb.append(quoteFix);
898                                                    sb.append(result);
899                                                    sb.append(quoteFix);
900                                                    sb.append(content.substring(y + 3, content.length()));
901    
902                                                    content = sb.toString();
903                                            }
904                                            else {
905                                                    processErrorMessage(
906                                                            fileName, "taglib: " + fileName + " " + lineCount);
907                                            }
908                                    }
909    
910                                    x = content.indexOf(quoteType + "<%=", y);
911    
912                                    if (x > matcher.end()) {
913                                            break;
914                                    }
915    
916                                    y = content.indexOf("%>" + quoteType, x);
917                            }
918                    }
919    
920                    return content;
921            }
922    
923            protected List<String> getJSPDuplicateImports(
924                    String fileName, String content, List<String> importLines) {
925    
926                    List<String> duplicateImports = new ArrayList<String>();
927    
928                    for (String importLine : importLines) {
929                            int x = content.indexOf("<%@ include file=");
930    
931                            if (x == -1) {
932                                    continue;
933                            }
934    
935                            int y = content.indexOf("<%@ page import=");
936    
937                            if (y == -1) {
938                                    continue;
939                            }
940    
941                            if ((x < y) && isJSPDuplicateImport(fileName, importLine, false)) {
942                                    duplicateImports.add(importLine);
943                            }
944                    }
945    
946                    return duplicateImports;
947            }
948    
949            protected String getTaglibRegex(String quoteType) {
950                    StringBuilder sb = new StringBuilder();
951    
952                    sb.append("<(");
953    
954                    for (int i = 0; i < _TAG_LIBRARIES.length; i++) {
955                            sb.append(_TAG_LIBRARIES[i]);
956                            sb.append(StringPool.PIPE);
957                    }
958    
959                    sb.deleteCharAt(sb.length() - 1);
960                    sb.append("):([^>]|%>)*");
961                    sb.append(quoteType);
962                    sb.append("<%=.*");
963                    sb.append(quoteType);
964                    sb.append(".*%>");
965                    sb.append(quoteType);
966                    sb.append("([^>]|%>)*>");
967    
968                    return sb.toString();
969            }
970    
971            protected String getVariableName(String line) {
972                    if (!line.endsWith(";") || line.startsWith("//")) {
973                            return null;
974                    }
975    
976                    String variableName = null;
977    
978                    int x = line.indexOf(" = ");
979    
980                    if (x == -1) {
981                            int y = line.lastIndexOf(" ");
982    
983                            if (y != -1) {
984                                    variableName = line.substring(y + 1, line.length() - 1);
985                            }
986                    }
987                    else {
988                            line = line.substring(0, x);
989    
990                            int y = line.lastIndexOf(" ");
991    
992                            if (y != -1) {
993                                    variableName = line.substring(y + 1);
994                            }
995                    }
996    
997                    if (Validator.isVariableName(variableName)) {
998                            return variableName;
999                    }
1000    
1001                    return null;
1002            }
1003    
1004            protected boolean hasUnusedJSPTerm(
1005                    String fileName, String regex, String type) {
1006    
1007                    _includeFileNames.add(fileName);
1008    
1009                    Set<String> checkedForUnusedJSPTerm = new HashSet<String>();
1010    
1011                    return !isJSPTermRequired(
1012                            fileName, regex, type, checkedForUnusedJSPTerm);
1013            }
1014    
1015            protected boolean hasUnusedTaglib(String fileName, String line) {
1016                    if (!line.startsWith("<%@ taglib uri=")) {
1017                            return false;
1018                    }
1019    
1020                    int x = line.indexOf(" prefix=");
1021    
1022                    if (x == -1) {
1023                            return false;
1024                    }
1025    
1026                    x = line.indexOf(StringPool.QUOTE, x);
1027    
1028                    int y = line.indexOf(StringPool.QUOTE, x + 1);
1029    
1030                    if ((x == -1) || (y == -1)) {
1031                            return false;
1032                    }
1033    
1034                    String taglibPrefix = line.substring(x + 1, y);
1035    
1036                    String regex = StringPool.LESS_THAN + taglibPrefix + StringPool.COLON;
1037    
1038                    return hasUnusedJSPTerm(fileName, regex, "taglib");
1039            }
1040    
1041            protected boolean hasUnusedVariable(String fileName, String line) {
1042                    if (line.contains(": ")) {
1043                            return false;
1044                    }
1045    
1046                    String variableName = getVariableName(line);
1047    
1048                    if (Validator.isNull(variableName) || variableName.equals("false") ||
1049                            variableName.equals("true")) {
1050    
1051                            return false;
1052                    }
1053    
1054                    String regex = "[^A-Za-z0-9_\"]" + variableName + "[^A-Za-z0-9_\"]";
1055    
1056                    return hasUnusedJSPTerm(fileName, regex, "variable");
1057            }
1058    
1059            protected boolean isJSPDuplicateImport(
1060                    String fileName, String importLine, boolean checkFile) {
1061    
1062                    String content = _jspContents.get(fileName);
1063    
1064                    if (Validator.isNull(content)) {
1065                            return false;
1066                    }
1067    
1068                    int x = importLine.indexOf("page");
1069    
1070                    if (x == -1) {
1071                            return false;
1072                    }
1073    
1074                    if (checkFile && content.contains(importLine.substring(x))) {
1075                            return true;
1076                    }
1077    
1078                    int y = content.indexOf("<%@ include file=");
1079    
1080                    if (y == -1) {
1081                            return false;
1082                    }
1083    
1084                    y = content.indexOf(StringPool.QUOTE, y);
1085    
1086                    if (y == -1) {
1087                            return false;
1088                    }
1089    
1090                    int z = content.indexOf(StringPool.QUOTE, y + 1);
1091    
1092                    if (z == -1) {
1093                            return false;
1094                    }
1095    
1096                    String includeFileName = content.substring(y + 1, z);
1097    
1098                    includeFileName = buildFullPathIncludeFileName(
1099                            fileName, includeFileName);
1100    
1101                    return isJSPDuplicateImport(includeFileName, importLine, true);
1102            }
1103    
1104            protected boolean isJSPTermRequired(
1105                    String fileName, String regex, String type,
1106                    Set<String> checkedForUnusedJSPTerm) {
1107    
1108                    if (checkedForUnusedJSPTerm.contains(fileName)) {
1109                            return false;
1110                    }
1111    
1112                    checkedForUnusedJSPTerm.add(fileName);
1113    
1114                    String content = _jspContents.get(fileName);
1115    
1116                    if (Validator.isNull(content)) {
1117                            return false;
1118                    }
1119    
1120                    Pattern pattern = Pattern.compile(regex);
1121    
1122                    Matcher matcher = pattern.matcher(content);
1123    
1124                    if (matcher.find() &&
1125                            (!type.equals("variable") || (checkedForUnusedJSPTerm.size() > 1) ||
1126                             matcher.find())) {
1127    
1128                            return true;
1129                    }
1130    
1131                    if (!_checkedForIncludesFileNames.contains(fileName)) {
1132                            addJSPIncludeFileNames(fileName);
1133    
1134                            if (fileName.endsWith("init.jsp") ||
1135                                    fileName.endsWith("init.jspf") ||
1136                                    fileName.contains("init-ext.jsp")) {
1137    
1138                                    addJSPReferenceFileNames(fileName);
1139                            }
1140                    }
1141    
1142                    _checkedForIncludesFileNames.add(fileName);
1143    
1144                    String[] includeFileNamesArray = _includeFileNames.toArray(
1145                            new String[_includeFileNames.size()]);
1146    
1147                    for (String includeFileName : includeFileNamesArray) {
1148                            if (!checkedForUnusedJSPTerm.contains(includeFileName) &&
1149                                    isJSPTermRequired(
1150                                            includeFileName, regex, type, checkedForUnusedJSPTerm)) {
1151    
1152                                    return true;
1153                            }
1154                    }
1155    
1156                    return false;
1157            }
1158    
1159            protected void moveFrequentlyUsedImportsToCommonInit(int minCount)
1160                    throws IOException {
1161    
1162                    if (_importCountMap.isEmpty()) {
1163                            return;
1164                    }
1165    
1166                    String commonInitFileName = "portal-web/docroot/html/common/init.jsp";
1167    
1168                    File commonInitFile = null;
1169                    String commonInitFileContent = null;
1170    
1171                    int x = -1;
1172    
1173                    for (Map.Entry<String, Integer> importCount :
1174                                    _importCountMap.entrySet()) {
1175    
1176                            Integer count = importCount.getValue();
1177    
1178                            if (count < minCount) {
1179                                    continue;
1180                            }
1181    
1182                            String importName = importCount.getKey();
1183    
1184                            int y = importName.lastIndexOf(StringPool.PERIOD);
1185    
1186                            String importClassName = importName.substring(y + 1);
1187    
1188                            if (_duplicateImportClassNames.contains(importClassName)) {
1189                                    continue;
1190                            }
1191    
1192                            if (commonInitFileContent == null) {
1193                                    commonInitFile = new File(commonInitFileName);
1194    
1195                                    commonInitFileContent = fileUtil.read(commonInitFile);
1196    
1197                                    x = commonInitFileContent.indexOf("<%@ page import");
1198                            }
1199    
1200                            commonInitFileContent = StringUtil.insert(
1201                                    commonInitFileContent,
1202                                    "<%@ page import=\"" + importName + "\" %>\n", x);
1203                    }
1204    
1205                    if (commonInitFileContent != null) {
1206                            fileUtil.write(commonInitFile, commonInitFileContent);
1207    
1208                            _jspContents.put(commonInitFileName, commonInitFileContent);
1209                    }
1210            }
1211    
1212            protected String stripJSPImports(String fileName, String content)
1213                    throws IOException {
1214    
1215                    fileName = fileName.replace(
1216                            CharPool.BACK_SLASH, CharPool.FORWARD_SLASH);
1217    
1218                    if (fileName.endsWith("init-ext.jsp")) {
1219                            return content;
1220                    }
1221    
1222                    Matcher matcher = _jspImportPattern.matcher(content);
1223    
1224                    if (!matcher.find()) {
1225                            return content;
1226                    }
1227    
1228                    String imports = matcher.group();
1229    
1230                    imports = StringUtil.replace(
1231                            imports, new String[] {"%><%@\r\n", "%><%@\n"},
1232                            new String[] {"%>\r\n<%@ ", "%>\n<%@ "});
1233    
1234                    List<String> importLines = new ArrayList<String>();
1235    
1236                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
1237                            new UnsyncStringReader(imports));
1238    
1239                    String line = null;
1240    
1241                    while ((line = unsyncBufferedReader.readLine()) != null) {
1242                            if (line.contains("import=")) {
1243                                    importLines.add(line);
1244                            }
1245                    }
1246    
1247                    List<String> unneededImports = getJSPDuplicateImports(
1248                            fileName, content, importLines);
1249    
1250                    addJSPUnusedImports(fileName, importLines, unneededImports);
1251    
1252                    for (String unneededImport : unneededImports) {
1253                            imports = StringUtil.replace(
1254                                    imports, unneededImport, StringPool.BLANK);
1255                    }
1256    
1257                    ImportsFormatter importsFormatter = new JSPImportsFormatter();
1258    
1259                    imports = importsFormatter.format(imports);
1260    
1261                    String beforeImports = content.substring(0, matcher.start());
1262    
1263                    if (Validator.isNull(imports)) {
1264                            beforeImports = StringUtil.replaceLast(
1265                                    beforeImports, "\n", StringPool.BLANK);
1266                    }
1267    
1268                    String afterImports = content.substring(matcher.end());
1269    
1270                    if (Validator.isNull(afterImports)) {
1271                            imports = StringUtil.replaceLast(imports, "\n", StringPool.BLANK);
1272    
1273                            content = beforeImports + imports;
1274    
1275                            return content;
1276                    }
1277    
1278                    content = beforeImports + imports + "\n" + afterImports;
1279    
1280                    return content;
1281            }
1282    
1283            private static final String[] _TAG_LIBRARIES = new String[] {
1284                    "aui", "c", "html", "jsp", "liferay-portlet", "liferay-security",
1285                    "liferay-theme", "liferay-ui", "liferay-util", "portlet", "struts",
1286                    "tiles"
1287            };
1288    
1289            private Set<String> _checkedForIncludesFileNames = new HashSet<String>();
1290            private List<String> _duplicateImportClassNames = new ArrayList<String>();
1291            private List<String> _importClassNames = new ArrayList<String>();
1292            private Map<String, Integer> _importCountMap =
1293                    new HashMap<String, Integer>();
1294            private Pattern _importsPattern = Pattern.compile("page import=\"(.+)\"");
1295            private Set<String> _includeFileNames = new HashSet<String>();
1296            private Pattern _javaClassPattern = Pattern.compile(
1297                    "\n(private|protected|public).* class ([\\s\\S]*?)\n\\}\n");
1298            private Map<String, String> _jspContents = new HashMap<String, String>();
1299            private Pattern _jspImportPattern = Pattern.compile(
1300                    "(<.*\n*page.import=\".*>\n*)+", Pattern.MULTILINE);
1301            private Pattern _jspIncludeFilePattern = Pattern.compile("/.*[.]jsp[f]?");
1302            private boolean _moveFrequentlyUsedImportsToCommonInit;
1303            private boolean _stripJSPImports = true;
1304            private Pattern _taglibLanguageKeyPattern1 = Pattern.compile(
1305                    "(?:confirmation|label|(?:M|m)essage|message key|names|title)=\"[^A-Z" +
1306                            "<=%\\[\\s]+\"");
1307            private Pattern _taglibLanguageKeyPattern2 = Pattern.compile(
1308                    "(aui:)(?:input|select|field-wrapper) (?!.*label=(?:'|\").+(?:'|\").*" +
1309                            "name=\"[^<=%\\[\\s]+\")(?!.*name=\"[^<=%\\[\\s]+\".*title=" +
1310                                    "(?:'|\").+(?:'|\"))(?!.*name=\"[^<=%\\[\\s]+\".*type=\"" +
1311                                            "hidden\").*name=\"([^<=%\\[\\s]+)\"");
1312            private Pattern _taglibLanguageKeyPattern3 = Pattern.compile(
1313                    "(liferay-ui:)(?:input-resource) .*id=\"([^<=%\\[\\s]+)\"(?!.*title=" +
1314                            "(?:'|\").+(?:'|\"))");
1315            private List<String> _unusedVariablesExclusions;
1316            private Pattern _xssPattern = Pattern.compile(
1317                    "\\s+([^\\s]+)\\s*=\\s*(Bean)?ParamUtil\\.getString\\(");
1318    
1319    }