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