diff --git a/jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifier.java b/jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifier.java index 555fc5c9e..adc1d04f2 100644 --- a/jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifier.java +++ b/jetlinks-components/notify-component/notify-webhook/src/main/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifier.java @@ -29,6 +29,7 @@ import reactor.core.publisher.Mono; import javax.annotation.Nonnull; +import java.net.URI; public class HttpWebHookNotifier extends AbstractNotifier { private final String id; @@ -73,7 +74,7 @@ public Mono send(@Nonnull HttpWebHookTemplate template, .method(template.getMethod()); if (StringUtils.hasText(template.getUrl())) { - bodyUriSpec.uri(template.getUrl()); + bodyUriSpec.uri(resolveTemplateUri(template.getUrl())); } if (method == HttpMethod.POST || method == HttpMethod.PUT @@ -103,6 +104,15 @@ public Mono send(@Nonnull HttpWebHookTemplate template, .bodyToMono(Void.class); } + URI resolveTemplateUri(String url) { + URI uri = URI.create(url); + // 模板只能补充相对路径,目标主机始终由具有配置权限的通知器配置决定。 + if (uri.isAbsolute() || uri.getHost() != null || url.startsWith("//")) { + throw new IllegalArgumentException("WebHook template URL must be relative"); + } + return uri; + } + @Nonnull @Override public Mono close() { diff --git a/jetlinks-components/notify-component/notify-webhook/src/test/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifierTest.java b/jetlinks-components/notify-component/notify-webhook/src/test/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifierTest.java new file mode 100644 index 000000000..e118be5cf --- /dev/null +++ b/jetlinks-components/notify-component/notify-webhook/src/test/java/org/jetlinks/community/notify/webhook/http/HttpWebHookNotifierTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 JetLinks https://www.jetlinks.cn + * + * Licensed 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.jetlinks.community.notify.webhook.http; + +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +class HttpWebHookNotifierTest { + + private final HttpWebHookNotifier notifier = new HttpWebHookNotifier( + "test", + new HttpWebHookProperties(), + WebClient.builder().baseUrl("https://example.com/api").build(), + mock(org.jetlinks.community.notify.template.TemplateManager.class) + ); + + @Test + void shouldAcceptRelativeTemplateUrl() { + assertEquals(URI.create("events/device-1"), notifier.resolveTemplateUri("events/device-1")); + } + + @Test + void shouldRejectAbsoluteTemplateUrl() { + assertThrows( + IllegalArgumentException.class, + () -> notifier.resolveTemplateUri("http://127.0.0.1/internal") + ); + assertThrows( + IllegalArgumentException.class, + () -> notifier.resolveTemplateUri("//127.0.0.1/internal") + ); + } +}