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