Skip to content

Commit 899ab00

Browse files
erwan-leforestier-sonarsourcesonartech
authored andcommitted
SONARPY-4684 Ignore IP-like version literals
GitOrigin-RevId: d5fefc668f25c922d96050cd460c0a980631089e
1 parent a357d61 commit 899ab00

2 files changed

Lines changed: 50 additions & 6 deletions

File tree

‎python-checks/src/main/java/org/sonar/python/checks/HardcodedIPCheck.java‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.sonar.check.Rule;
2525
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2626
import org.sonar.plugins.python.api.tree.AnnotatedAssignment;
27+
import org.sonar.plugins.python.api.tree.AssignmentExpression;
2728
import org.sonar.plugins.python.api.tree.AssignmentStatement;
2829
import org.sonar.plugins.python.api.tree.Expression;
2930
import org.sonar.plugins.python.api.tree.ExpressionList;
@@ -44,6 +45,7 @@ public class HardcodedIPCheck extends PythonSubscriptionCheck {
4445
private static final String IPV6_ALONE = ("(?<ipv6>(" + IPV6_NO_PREFIX_COMPRESSION + "|" + IPV6_PREFIX_COMPRESSION + ")??(:?" + IPV4_ALONE + ")?" + ")");
4546
private static final String IPV6_URL = "([^\\d.]*/)?\\[" + IPV6_ALONE + "]((:\\d{1,5})?(?!\\d|\\.))(/.*)?";
4647

48+
private static final Pattern IPV4_ALONE_REGEX = Pattern.compile(IPV4_ALONE);
4749
private static final Pattern IPV4_URL_REGEX = Pattern.compile("([^\\d.]*/)?" + IPV4_ALONE + "((:\\d{1,5})?(?!\\d|\\.))(/.*)?");
4850
private static final List<Pattern> IPV6_REGEX_LIST = Arrays.asList(
4951
Pattern.compile(IPV6_ALONE),
@@ -72,10 +74,10 @@ public void initialize(Context context) {
7274
return;
7375
}
7476
StringLiteral stringLiteral = (StringLiteral) ctx.syntaxNode();
75-
if (isMultilineString(stringLiteral) || isVersionLiteral(stringLiteral)) {
77+
String content = Expressions.unescape(stringLiteral);
78+
if (isMultilineString(stringLiteral) || isVersionLiteral(stringLiteral, content)) {
7679
return;
7780
}
78-
String content = Expressions.unescape(stringLiteral);
7981
Matcher matcher = IPV4_URL_REGEX.matcher(content);
8082
if (matcher.matches()) {
8183
String ip = matcher.group("ipv4");
@@ -97,7 +99,10 @@ public void initialize(Context context) {
9799
});
98100
}
99101

100-
private static boolean isVersionLiteral(StringLiteral stringLiteral) {
102+
private static boolean isVersionLiteral(StringLiteral stringLiteral, String content) {
103+
if (!IPV4_ALONE_REGEX.matcher(content).matches()) {
104+
return false;
105+
}
101106
Expression assignedValue = stringLiteral;
102107
while (assignedValue.parent() instanceof ParenthesizedExpression parenthesizedExpression) {
103108
assignedValue = parenthesizedExpression;
@@ -106,6 +111,9 @@ private static boolean isVersionLiteral(StringLiteral stringLiteral) {
106111
if (parent instanceof AssignmentStatement assignment) {
107112
return assignment.assignedValue() == assignedValue && hasVersionName(assignment);
108113
}
114+
if (parent instanceof AssignmentExpression assignment) {
115+
return assignment.expression() == assignedValue && isVersionName(assignment.lhsName());
116+
}
109117
return parent instanceof AnnotatedAssignment assignment
110118
&& assignment.assignedValue() == assignedValue
111119
&& isVersionName(assignment.variable());
@@ -120,7 +128,32 @@ private static boolean hasVersionName(AssignmentStatement assignment) {
120128
}
121129

122130
private static boolean isVersionName(Expression expression) {
123-
return Expressions.removeParentheses(expression) instanceof Name name && "__version__".equals(name.name());
131+
return Expressions.removeParentheses(expression) instanceof Name name
132+
&& containsVersionWord(name.name());
133+
}
134+
135+
private static boolean containsVersionWord(String identifier) {
136+
int wordStart = 0;
137+
for (int index = 1; index < identifier.length(); index++) {
138+
if (isIdentifierWordBoundary(identifier, index)) {
139+
if ("version".equalsIgnoreCase(identifier.substring(wordStart, index))) {
140+
return true;
141+
}
142+
wordStart = index;
143+
}
144+
}
145+
return "version".equalsIgnoreCase(identifier.substring(wordStart));
146+
}
147+
148+
private static boolean isIdentifierWordBoundary(String identifier, int index) {
149+
char previous = identifier.charAt(index - 1);
150+
char current = identifier.charAt(index);
151+
return previous == '_'
152+
|| current == '_'
153+
|| Character.isDigit(previous) != Character.isDigit(current)
154+
|| (Character.isLowerCase(previous) && Character.isUpperCase(current))
155+
|| (Character.isUpperCase(previous) && Character.isUpperCase(current)
156+
&& index + 1 < identifier.length() && Character.isLowerCase(identifier.charAt(index + 1)));
124157
}
125158

126159
private static boolean isMultilineString(StringLiteral pyStringLiteralTree) {

‎python-checks/src/test/resources/checks/hardcodedIP.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,19 @@
2626
__version__ = ("26.8.0.1") # Compliant
2727
__version__: str = "26.8.0.1" # Compliant
2828
(__version__) = "26.8.0.1" # Compliant
29-
version = "26.8.0.1" # Noncompliant
30-
VERSION = "26.8.0.1" # Noncompliant
29+
version = "26.8.0.1" # Compliant
30+
APP_VERSION = "26.8.0.1" # Compliant
31+
apiVersion = "26.8.0.1" # Compliant
32+
APIVersion = "26.8.0.1" # Compliant
33+
version2 = "26.8.0.1" # Compliant
34+
if (apiVersion := "26.8.0.1"):
35+
pass
36+
version_url = "http://192.168.0.1/admin.html" # Noncompliant
37+
version_with_port = "1.2.3.4:80" # Noncompliant
38+
version_ipv6 = "1080:0:0:0:8:800:200C:417A" # Noncompliant
39+
conversion = "26.8.0.1" # Noncompliant
40+
subversion = "26.8.0.1" # Noncompliant
41+
host = "26.8.0.1" # Noncompliant
3142
__version__ = version = "26.8.0.1" # Noncompliant
3243
metadata.__version__ = "26.8.0.1" # Noncompliant
3344
versions["__version__"] = "26.8.0.1" # Noncompliant

0 commit comments

Comments
 (0)