This is an automated email from the ASF dual-hosted git repository.

dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 824bcf5ebc fix: prevent ArrayIndexOutOfBoundsException in K8s ingress 
protocol annotation parsing (#6892)
824bcf5ebc is described below

commit 824bcf5ebc2794cc5f85676f99e31ed0e501b8fd
Author: wy471x <[email protected]>
AuthorDate: Tue Sep 15 22:39:30 2026 +0800

    fix: prevent ArrayIndexOutOfBoundsException in K8s ingress protocol 
annotation parsing (#6892)
    
    - DivideIngressParser: add bounds check when indexing protocol array, fall 
back to "http://"; when annotation has fewer entries than endpoint addresses
    - DubboIngressParser: fix NPE when annotation is missing, fix 
double-increment bug where protocols[i++] was evaluated twice in one expression
    - Add unit tests covering missing, exact, fewer, mixed, and empty 
annotation scenarios
    
    Co-authored-by: Claude Opus 4.7 <[email protected]>
    Co-authored-by: zhengpeng <[email protected]>
    Co-authored-by: Liming Deng <[email protected]>
---
 .../shenyu/k8s/parser/DivideIngressParser.java     |   7 +-
 .../shenyu/k8s/parser/DubboIngressParser.java      |  12 +-
 .../shenyu/k8s/parser/DivideIngressParserTest.java | 153 ++++++++++++++++++++
 .../shenyu/k8s/parser/DubboIngressParserTest.java  | 155 +++++++++++++++++++++
 4 files changed, 320 insertions(+), 7 deletions(-)

diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
index ac5b287eac..d32aebaa97 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
@@ -314,15 +314,16 @@ public class DivideIngressParser implements 
K8sResourceParser<V1Ingress> {
                     if (Objects.isNull(addresses) || addresses.isEmpty()) {
                         continue;
                     }
-                    int i = 0;
-                    for (V1EndpointAddress address : addresses) {
+                    for (int i = 0; i < addresses.size(); i++) {
+                        V1EndpointAddress address = addresses.get(i);
                         String upstreamIp = address.getIp();
                         String defaultPort = parsePort(backend.getService());
                         if (Objects.nonNull(defaultPort)) {
+                            String upstreamProtocol = Objects.isNull(protocol) 
|| i >= protocol.length ? "http://"; : protocol[i];
                             DivideUpstream upstream = new DivideUpstream();
                             upstream.setUpstreamUrl(upstreamIp + ":" + 
defaultPort);
                             upstream.setWeight(100);
-                            upstream.setProtocol(Objects.isNull(protocol) ? 
"http://"; : protocol[i++]);
+                            upstream.setProtocol(upstreamProtocol);
                             upstream.setWarmup(0);
                             upstream.setStatus(true);
                             upstream.setUpstreamHost("");
diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java
index 7c76d9c939..4a2e077dcd 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java
@@ -336,7 +336,10 @@ public class DubboIngressParser implements 
K8sResourceParser<V1Ingress> {
             String serviceName = path.getBackend().getService().getName();
             V1Endpoints v1Endpoints = 
endpointsLister.namespace(namespace).get(serviceName);
             List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
-            String[] protocols = 
annotations.get(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY).split(",");
+            String[] protocols = null;
+            if (Objects.nonNull(annotations) && 
annotations.containsKey(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY)) {
+                protocols = 
annotations.get(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY).split(",");
+            }
             if (Objects.isNull(subsets) || CollectionUtils.isEmpty(subsets)) {
                 LOG.info("Endpoints {} do not have subsets", serviceName);
             } else {
@@ -345,15 +348,16 @@ public class DubboIngressParser implements 
K8sResourceParser<V1Ingress> {
                     if (Objects.isNull(addresses) || addresses.isEmpty()) {
                         continue;
                     }
-                    int i = 0;
-                    for (V1EndpointAddress address : addresses) {
+                    for (int i = 0; i < addresses.size(); i++) {
+                        V1EndpointAddress address = addresses.get(i);
                         String upstreamIp = address.getIp();
                         String defaultPort = 
parsePort(path.getBackend().getService());
                         if (Objects.nonNull(defaultPort)) {
+                            String upstreamProtocol = 
Objects.isNull(protocols) || i >= protocols.length ? "dubbo://" : protocols[i];
                             DubboUpstream upstream = DubboUpstream.builder()
                                     .upstreamUrl(upstreamIp + ":" + 
defaultPort)
                                     .weight(100)
-                                    .protocol(Objects.isNull(protocols[i++]) ? 
"dubbo://" : protocols[i++])
+                                    .protocol(upstreamProtocol)
                                     .warmup(0)
                                     .status(true)
                                     .upstreamHost("")
diff --git 
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DivideIngressParserTest.java
 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DivideIngressParserTest.java
new file mode 100644
index 0000000000..aad81bb842
--- /dev/null
+++ 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DivideIngressParserTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.k8s.parser;
+
+import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.models.V1EndpointAddress;
+import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
+import io.kubernetes.client.openapi.models.V1HTTPIngressPathBuilder;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1IngressBuilder;
+import io.kubernetes.client.openapi.models.V1IngressRuleBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import org.apache.shenyu.common.dto.convert.selector.DivideUpstream;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.k8s.common.IngressConstants;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for DivideIngressParser upstream protocol parsing.
+ */
+public class DivideIngressParserTest {
+
+    private Lister<V1Service> serviceLister;
+
+    private Indexer<V1Endpoints> endpointsIndexer;
+
+    private Lister<V1Endpoints> endpointsLister;
+
+    @BeforeEach
+    @SuppressWarnings("unchecked")
+    public void setUp() {
+        serviceLister = new Lister<>(mock(Indexer.class));
+        endpointsIndexer = mock(Indexer.class);
+        endpointsLister = new Lister<>(endpointsIndexer);
+    }
+
+    private List<DivideUpstream> parseAndGetUpstreams(final Map<String, 
String> annotations) {
+        V1Endpoints endpoints = new V1EndpointsBuilder()
+                
.withNewMetadata().withNamespace("test").withName("testService").endMetadata()
+                .withSubsets(new V1EndpointSubsetBuilder()
+                        .withAddresses(new V1EndpointAddress().ip("10.0.0.1"),
+                                new V1EndpointAddress().ip("10.0.0.2"),
+                                new V1EndpointAddress().ip("10.0.0.3"))
+                        .build())
+                .build();
+        
when(endpointsIndexer.getByKey("test/testService")).thenReturn(endpoints);
+
+        Map<String, String> allAnnotations = new HashMap<>();
+        allAnnotations.put("kubernetes.io/ingress.class", "shenyu");
+        if (Objects.nonNull(annotations)) {
+            allAnnotations.putAll(annotations);
+        }
+
+        V1Ingress ingress = new V1IngressBuilder()
+                
.withNewMetadata().withName("testIngress").withNamespace("test").withAnnotations(allAnnotations).endMetadata()
+                .withNewSpec().withRules(
+                        new V1IngressRuleBuilder().withNewHttp().withPaths(
+                                new 
V1HTTPIngressPathBuilder().withPath("/test")
+                                        .withNewBackend()
+                                            
.withNewService().withName("testService").withNewPort().withNumber(8080).endPort().endService()
+                                        .endBackend().build())
+                                .endHttp().build())
+                .endSpec()
+                .build();
+
+        DivideIngressParser parser = new DivideIngressParser(serviceLister, 
endpointsLister);
+        ShenyuMemoryConfig result = parser.parse(ingress, null);
+
+        String handle = 
result.getRouteConfigList().get(0).getSelectorData().getHandle();
+        return GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
+    }
+
+    @Test
+    public void testProtocolAnnotationMissing() {
+        List<DivideUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(null));
+        assertEquals(3, upstreams.size());
+        for (DivideUpstream upstream : upstreams) {
+            assertEquals("http://";, upstream.getProtocol());
+        }
+    }
+
+    @Test
+    public void testProtocolAnnotationExactMatch() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"https://,https://,https://";);
+        List<DivideUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        for (DivideUpstream upstream : upstreams) {
+            assertEquals("https://";, upstream.getProtocol());
+        }
+    }
+
+    @Test
+    public void testProtocolAnnotationFewerThanAddresses() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"https://";);
+        List<DivideUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        assertEquals("https://";, upstreams.get(0).getProtocol());
+        assertEquals("http://";, upstreams.get(1).getProtocol());
+        assertEquals("http://";, upstreams.get(2).getProtocol());
+    }
+
+    @Test
+    public void testProtocolAnnotationMixed() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"https://,http://";);
+        List<DivideUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        assertEquals("https://";, upstreams.get(0).getProtocol());
+        assertEquals("http://";, upstreams.get(1).getProtocol());
+        assertEquals("http://";, upstreams.get(2).getProtocol());
+    }
+
+    @Test
+    public void testEmptyProtocolAnnotation() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"");
+        List<DivideUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertNotNull(upstreams);
+    }
+}
diff --git 
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java
 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java
new file mode 100644
index 0000000000..b53291f074
--- /dev/null
+++ 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.k8s.parser;
+
+import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.models.V1EndpointAddress;
+import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
+import io.kubernetes.client.openapi.models.V1HTTPIngressPathBuilder;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1IngressBuilder;
+import io.kubernetes.client.openapi.models.V1IngressRuleBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import org.apache.shenyu.common.dto.convert.selector.DubboUpstream;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.k8s.common.IngressConstants;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for DubboIngressParser upstream protocol parsing.
+ */
+public class DubboIngressParserTest {
+
+    private Lister<V1Service> serviceLister;
+
+    private Indexer<V1Endpoints> endpointsIndexer;
+
+    private Lister<V1Endpoints> endpointsLister;
+
+    @BeforeEach
+    @SuppressWarnings("unchecked")
+    public void setUp() {
+        serviceLister = new Lister<>(mock(Indexer.class));
+        endpointsIndexer = mock(Indexer.class);
+        endpointsLister = new Lister<>(endpointsIndexer);
+    }
+
+    private List<DubboUpstream> parseAndGetUpstreams(final Map<String, String> 
annotations) {
+        V1Endpoints endpoints = new V1EndpointsBuilder()
+                
.withNewMetadata().withNamespace("test").withName("testService").endMetadata()
+                .withSubsets(new V1EndpointSubsetBuilder()
+                        .withAddresses(new V1EndpointAddress().ip("10.0.0.1"),
+                                new V1EndpointAddress().ip("10.0.0.2"),
+                                new V1EndpointAddress().ip("10.0.0.3"))
+                        .build())
+                .build();
+        
when(endpointsIndexer.getByKey("test/testService")).thenReturn(endpoints);
+
+        Map<String, String> allAnnotations = new HashMap<>();
+        allAnnotations.put("kubernetes.io/ingress.class", "shenyu");
+        if (Objects.nonNull(annotations)) {
+            allAnnotations.putAll(annotations);
+        }
+        Map<String, String> labels = new HashMap<>();
+
+        V1Ingress ingress = new V1IngressBuilder()
+                
.withNewMetadata().withName("testIngress").withNamespace("test")
+                    
.withAnnotations(allAnnotations).withLabels(labels).endMetadata()
+                .withNewSpec().withRules(
+                        new V1IngressRuleBuilder().withNewHttp().withPaths(
+                                new 
V1HTTPIngressPathBuilder().withPath("/test")
+                                        .withNewBackend()
+                                            
.withNewService().withName("testService").withNewPort().withNumber(20880).endPort().endService()
+                                        .endBackend().build())
+                                .endHttp().build())
+                .endSpec()
+                .build();
+
+        DubboIngressParser parser = new DubboIngressParser(serviceLister, 
endpointsLister);
+        ShenyuMemoryConfig result = parser.parse(ingress, null);
+
+        String handle = 
result.getRouteConfigList().get(0).getSelectorData().getHandle();
+        return GsonUtils.getInstance().fromList(handle, DubboUpstream.class);
+    }
+
+    @Test
+    public void testProtocolAnnotationMissing() {
+        List<DubboUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(null));
+        assertEquals(3, upstreams.size());
+        for (DubboUpstream upstream : upstreams) {
+            assertEquals("dubbo://", upstream.getProtocol());
+        }
+    }
+
+    @Test
+    public void testProtocolAnnotationExactMatch() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"dubbo://,dubbo://,dubbo://");
+        List<DubboUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        for (DubboUpstream upstream : upstreams) {
+            assertEquals("dubbo://", upstream.getProtocol());
+        }
+    }
+
+    @Test
+    public void testProtocolAnnotationFewerThanAddresses() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"triple://");
+        List<DubboUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        assertEquals("triple://", upstreams.get(0).getProtocol());
+        assertEquals("dubbo://", upstreams.get(1).getProtocol());
+        assertEquals("dubbo://", upstreams.get(2).getProtocol());
+    }
+
+    @Test
+    public void testProtocolAnnotationMixed() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"triple://,dubbo://");
+        List<DubboUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertEquals(3, upstreams.size());
+        assertEquals("triple://", upstreams.get(0).getProtocol());
+        assertEquals("dubbo://", upstreams.get(1).getProtocol());
+        assertEquals("dubbo://", upstreams.get(2).getProtocol());
+    }
+
+    @Test
+    public void testEmptyProtocolAnnotation() {
+        Map<String, String> annotations = new HashMap<>();
+        annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY, 
"");
+        List<DubboUpstream> upstreams = assertDoesNotThrow(() -> 
parseAndGetUpstreams(annotations));
+        assertNotNull(upstreams);
+    }
+}

Reply via email to