You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.classloader;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Optional;
  26. import java.util.Set;
  27. import javax.annotation.Nullable;
  28. /**
  29. * A mask restricts access of a classloader to resources through inclusion and exclusion patterns.
  30. * By default all resources/classes are visible.
  31. * <p/>
  32. * Format of inclusion/exclusion patterns is the file path separated by slashes, for example
  33. * "org/foo/Bar.class" or "org/foo/config.xml". Wildcard patterns are not supported. Directories must end with
  34. * slash, for example "org/foo/" for excluding package org.foo and its sub-packages. Add the
  35. * exclusion "/" to exclude everything.
  36. *
  37. * @since 0.1
  38. */
  39. public class Mask {
  40. private static final String ROOT = "/";
  41. /**
  42. * Accepts everything
  43. *
  44. * @since 1.1
  45. */
  46. public static final Mask ALL = Mask.builder().build();
  47. /**
  48. * Accepts nothing
  49. *
  50. * @since 1.1
  51. */
  52. public static final Mask NONE = Mask.builder().exclude(ROOT).build();
  53. private final Set<String> inclusions;
  54. private final Set<String> exclusions;
  55. private Mask(Builder builder) {
  56. this.inclusions = Collections.unmodifiableSet(builder.inclusions);
  57. this.exclusions = Collections.unmodifiableSet(builder.exclusions);
  58. }
  59. /**
  60. * Create a {@link Builder} for building immutable instances of {@link Mask}
  61. *
  62. * @since 1.1
  63. */
  64. public static Builder builder() {
  65. return new Builder();
  66. }
  67. public Set<String> getInclusions() {
  68. return inclusions;
  69. }
  70. public Set<String> getExclusions() {
  71. return exclusions;
  72. }
  73. boolean acceptClass(String classname) {
  74. if (inclusions.isEmpty() && exclusions.isEmpty()) {
  75. return true;
  76. }
  77. return acceptResource(classToResource(classname));
  78. }
  79. boolean acceptResource(String name) {
  80. boolean ok = true;
  81. if (!inclusions.isEmpty()) {
  82. ok = false;
  83. for (String include : inclusions) {
  84. if (matchPattern(name, include)) {
  85. ok = true;
  86. break;
  87. }
  88. }
  89. }
  90. if (ok) {
  91. for (String exclude : exclusions) {
  92. if (matchPattern(name, exclude)) {
  93. ok = false;
  94. break;
  95. }
  96. }
  97. }
  98. return ok;
  99. }
  100. private static boolean matchPattern(String name, String pattern) {
  101. return pattern.equals(ROOT) || (pattern.endsWith("/") && name.startsWith(pattern)) || pattern.equals(name);
  102. }
  103. private static String classToResource(String classname) {
  104. return classname.replace('.', '/') + ".class";
  105. }
  106. public static class Builder {
  107. private final Set<String> inclusions = new HashSet<>();
  108. private final Set<String> exclusions = new HashSet<>();
  109. private Builder() {
  110. }
  111. public Builder include(String path, String... others) {
  112. doInclude(path);
  113. for (String other : others) {
  114. doInclude(other);
  115. }
  116. return this;
  117. }
  118. public Builder exclude(String path, String... others) {
  119. doExclude(path);
  120. for (String other : others) {
  121. doExclude(other);
  122. }
  123. return this;
  124. }
  125. public Builder copy(Mask with) {
  126. this.inclusions.addAll(with.inclusions);
  127. this.exclusions.addAll(with.exclusions);
  128. return this;
  129. }
  130. public Builder merge(Mask with) {
  131. List<String> lowestIncludes = new ArrayList<>();
  132. if (inclusions.isEmpty()) {
  133. lowestIncludes.addAll(with.inclusions);
  134. } else if (with.inclusions.isEmpty()) {
  135. lowestIncludes.addAll(inclusions);
  136. } else {
  137. for (String include : inclusions) {
  138. for (String fromInclude : with.inclusions) {
  139. overlappingInclude(include, fromInclude)
  140. .ifPresent(lowestIncludes::add);
  141. }
  142. }
  143. }
  144. inclusions.clear();
  145. inclusions.addAll(lowestIncludes);
  146. exclusions.addAll(with.exclusions);
  147. return this;
  148. }
  149. private static Optional<String> overlappingInclude(String include, String fromInclude) {
  150. if (fromInclude.equals(include)) {
  151. return Optional.of(fromInclude);
  152. } else if (fromInclude.startsWith(include)) {
  153. return Optional.of(fromInclude);
  154. } else if (include.startsWith(fromInclude)) {
  155. return Optional.of(include);
  156. }
  157. return Optional.empty();
  158. }
  159. public Mask build() {
  160. return new Mask(this);
  161. }
  162. private void doInclude(@Nullable String path) {
  163. this.inclusions.add(validatePath(path));
  164. }
  165. private void doExclude(@Nullable String path) {
  166. this.exclusions.add(validatePath(path));
  167. }
  168. private static String validatePath(@Nullable String path) {
  169. if (path == null) {
  170. throw new IllegalArgumentException("Mask path must not be null");
  171. }
  172. if (path.startsWith("/") && path.length() > 1) {
  173. throw new IllegalArgumentException("Mask path must not start with slash: ");
  174. }
  175. if (path.contains("*")) {
  176. throw new IllegalArgumentException("Mask path is not a wildcard pattern and should not contain star characters (*): " + path);
  177. }
  178. return path;
  179. }
  180. }
  181. }