diff --git a/its/ruling/src/test/resources/expected/python-S5332.json b/its/ruling/src/test/resources/expected/python-S5332.json index 10e1c51d01..e9fe419aa9 100644 --- a/its/ruling/src/test/resources/expected/python-S5332.json +++ b/its/ruling/src/test/resources/expected/python-S5332.json @@ -2,6 +2,9 @@ 'project:buildbot-0.8.6p1/buildbot/config.py':[ 56, ], +'project:buildbot-0.8.6p1/buildbot/revlinks.py':[ +47, +], 'project:buildbot-0.8.6p1/buildbot/status/web/feeds.py':[ 74, ], @@ -20,9 +23,15 @@ ], 'project:buildbot-0.8.6p1/buildbot/test/unit/test_db_changes.py':[ 65, +83, +97, +], +'project:buildbot-0.8.6p1/buildbot/test/unit/test_master.py':[ +60, ], 'project:buildbot-0.8.6p1/buildbot/test/unit/test_revlinks.py':[ 40, +58, 60, 80, 82, @@ -52,6 +61,14 @@ 65, 73, 78, +110, +115, +122, +123, +124, +135, +136, +137, 178, 188, 190, @@ -373,13 +390,6 @@ 47, 50, ], -'project:tornado-2.3/demos/benchmark/benchmark.py':[ -70, -], -'project:tornado-2.3/demos/benchmark/chunk_benchmark.py':[ -35, -41, -], 'project:tornado-2.3/setup.py':[ 54, 55, @@ -397,6 +407,7 @@ 135, 136, 145, +162, 180, 181, 182, @@ -409,6 +420,7 @@ 464, 520, 643, +741, 818, 900, ], @@ -434,34 +446,21 @@ 80, 83, 86, +93, 126, ], 'project:tornado-2.3/tornado/test/httpclient_test.py':[ -143, 164, 164, ], -'project:tornado-2.3/tornado/test/process_test.py':[ -57, -], 'project:tornado-2.3/tornado/test/simple_httpclient_test.py':[ 279, 279, ], -'project:tornado-2.3/tornado/test/twisted_test.py':[ -406, -412, -418, -424, -], 'project:tornado-2.3/tornado/test/web_test.py':[ 195, -210, 367, ], -'project:tornado-2.3/tornado/testing.py':[ -275, -], 'project:tornado-2.3/tornado/web.py':[ 1375, ], @@ -577,29 +576,20 @@ 37, 66, ], -'project:twisted-12.1.0/twisted/web/test/test_cgi.py':[ -106, -123, -141, -159, -176, -190, -207, -], 'project:twisted-12.1.0/twisted/web/test/test_distrib.py':[ -90, 137, 156, ], +'project:twisted-12.1.0/twisted/web/test/test_http.py':[ +954, +], 'project:twisted-12.1.0/twisted/web/test/test_proxy.py':[ 437, 495, ], -'project:twisted-12.1.0/twisted/web/test/test_soap.py':[ -68, -], 'project:twisted-12.1.0/twisted/web/test/test_util.py':[ 37, +53, ], 'project:twisted-12.1.0/twisted/web/test/test_web.py':[ 46, @@ -616,10 +606,8 @@ 223, 228, 233, +243, 270, -377, -849, -908, 933, 988, 1000, @@ -669,16 +657,8 @@ 760, ], 'project:twisted-12.1.0/twisted/web/test/test_xmlrpc.py':[ -285, -353, -363, -455, -475, -547, 647, -655, 663, -743, ], 'project:twisted-12.1.0/twisted/web/topfiles/setup.py':[ 23, diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java index 47a790f6c8..835951c6df 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java @@ -38,7 +38,7 @@ @Rule(key = "S5332") public class ClearTextProtocolsCheck extends PythonSubscriptionCheck { private static final List SENSITIVE_PROTOCOLS = Arrays.asList("http://", "ftp://", "telnet://"); - private static final Pattern LOOPBACK = Pattern.compile("^localhost$|^127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1$", Pattern.CASE_INSENSITIVE); + private static final Pattern LOOPBACK = Pattern.compile("localhost|127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1", Pattern.CASE_INSENSITIVE); private static final Map ALTERNATIVES = new HashMap<>(); static { @@ -59,7 +59,7 @@ public void initialize(Context context) { .ifPresent(protocol -> ctx.addIssue(node, message(protocol))); }); context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> { - Symbol symbol = ctx.symbolTable().getSymbol(((PyCallExpressionTree) ctx.syntaxNode())); + Symbol symbol = ctx.symbolTable().getSymbol((PyCallExpressionTree) ctx.syntaxNode()); isUnsafeLib(symbol).ifPresent(protocol -> ctx.addIssue(ctx.syntaxNode(), message(protocol))); }); } @@ -78,7 +78,10 @@ private static Optional unsafeProtocol(String literalValue) { return Optional.empty(); } } catch (URISyntaxException e) { - // not parseable uri + // not parseable uri, try to find loopback in the substring without protocol, this handles case of url formatted as string + if (LOOPBACK.matcher(literalValue.substring(protocol.length())).find()) { + return Optional.empty(); + } } return Optional.of(protocol); } diff --git a/python-checks/src/test/resources/checks/hotspots/clearTextProtocols.py b/python-checks/src/test/resources/checks/hotspots/clearTextProtocols.py index 5865f2ee95..e6a62eaaf3 100644 --- a/python-checks/src/test/resources/checks/hotspots/clearTextProtocols.py +++ b/python-checks/src/test/resources/checks/hotspots/clearTextProtocols.py @@ -81,3 +81,18 @@ def download(url='ssh://exemple.com'): # Compliant url = "http://0000:0:0000::1" # Compliant url = "http://0::0:1" # Compliant url = "ftp://user@localhost" # Compliant + +self.server_url = 'http://127.0.0.1:%s' % self.server.port # compliant, loopback +def get_url(self, path): + return "http://127.0.0.1:%d%s" % (self.port, path) # compliant, loopback + +data = self.urlopen("http://localhost:%s/" % handler.port) # compliant, loopback + + +gravatar_url = u'http://www.gravatar.com/avatar/{0}?{1}'.format( # Noncompliant + hashlib.md5(self.user.email.lower()).hexdigest(), + urllib.urlencode({'d': no_picture, 's': '256'}) +) + +config = "http://cens.ioc.ee/projects/f2py2e/2.x"\ + "/F2PY-2-latest.tar.gz" #false negative, requires multiline string support diff --git a/python-squid/src/main/java/org/sonar/python/tree/PyStringLiteralTreeImpl.java b/python-squid/src/main/java/org/sonar/python/tree/PyStringLiteralTreeImpl.java index c8284a6c02..fdffaf3b34 100644 --- a/python-squid/src/main/java/org/sonar/python/tree/PyStringLiteralTreeImpl.java +++ b/python-squid/src/main/java/org/sonar/python/tree/PyStringLiteralTreeImpl.java @@ -57,6 +57,31 @@ public String value() { @Override public String trimmedQuotesValue() { - return value.substring(1, value.length() - 1); + String trimmed = removePrefix(value); + // determine if string is using long string or short string format + int startIndex = 1; + if (isTripleQuote(trimmed)) { + startIndex = 3; + } + return trimmed.substring(startIndex, trimmed.length() - startIndex); + } + + private boolean isTripleQuote(String trimmed) { + if (trimmed.length() >= 6) { + char startChar = trimmed.charAt(0); + return startChar == trimmed.charAt(1) && startChar == trimmed.charAt(2); + } + return false; + } + + private static String removePrefix(String value) { + if (isCharQuote(value.charAt(0))) { + return value; + } + return removePrefix(value.substring(1)); + } + + private static boolean isCharQuote(char character) { + return character == '\'' || character == '\"'; } } diff --git a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java index b499f91616..bb6df1a1c6 100644 --- a/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java +++ b/python-squid/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java @@ -1283,11 +1283,25 @@ public void numeric_literal_expression() { @Test public void string_literal_expression() { setRootRule(PythonGrammar.ATOM); - PyExpressionTree parse = parse("\"plop\"", treeMaker::expression); + assertStringLiteral("''", ""); + assertStringLiteral("'\"'", "\""); + assertStringLiteral("'\"\"\"\"\"'", "\"\"\"\"\""); + assertStringLiteral("\"plop\"", "plop"); + assertStringLiteral("u\'plop\'", "plop"); + assertStringLiteral("b\"abcdef\"", "abcdef"); + assertStringLiteral("f\"\"\"Eric Idle\"\"\"", "Eric Idle"); + assertStringLiteral("fr'x={4*10}'", "x={4*10}"); + assertStringLiteral("f'He said his name is {name} and he is {age} years old.'", "He said his name is {name} and he is {age} years old."); + assertStringLiteral("f'''He said his name is {name.upper()}\n ... and he is {6 * seven} years old.'''", + "He said his name is {name.upper()}\n ... and he is {6 * seven} years old."); + } + + private void assertStringLiteral(String fullValue, String trimmedQuoteValue) { + PyExpressionTree parse = parse(fullValue, treeMaker::expression); assertThat(parse.is(Tree.Kind.STRING_LITERAL)).isTrue(); PyStringLiteralTree stringLiteral = (PyStringLiteralTree) parse; - assertThat(stringLiteral.value()).isEqualTo("\"plop\""); - assertThat(stringLiteral.trimmedQuotesValue()).isEqualTo("plop"); + assertThat(stringLiteral.value()).isEqualTo(fullValue); + assertThat(stringLiteral.trimmedQuotesValue()).isEqualTo(trimmedQuoteValue); assertThat(stringLiteral.children()).isEmpty(); }