Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 8 additions & 35 deletions src/main/java/core/packetproxy/common/ConfigHttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import packetproxy.gui.GUIMain;
import packetproxy.model.*;

public class ConfigHttpServer extends NanoHTTPD {

private final String allowedAccessToken;
private final ConfigHttpUiActions uiActions;
private final ConfigSettingsWriter settingsWriter;

public ConfigHttpServer(String hostname, int port, String allowedAccessToken) {
public ConfigHttpServer(String hostname, int port, String allowedAccessToken, ConfigHttpUiActions uiActions,
ConfigSettingsWriter settingsWriter) {
super(hostname, port);
this.allowedAccessToken = allowedAccessToken;
this.uiActions = uiActions;
this.settingsWriter = settingsWriter;
}

private void fixUpServerList(Map<Integer, Integer> serverMap, List<Server> serverList) {
Expand Down Expand Up @@ -117,18 +120,7 @@ public Response serve(IHTTPSession session) {

try {

GUIMain.getInstance().setAlwaysOnTop(true);
GUIMain.getInstance().setVisible(true);

GUIMain.getInstance().getTabbedPane().setSelectedIndex(GUIMain.Panes.OPTIONS.ordinal());

int option = JOptionPane.showConfirmDialog(GUIMain.getInstance(),
I18nString.get("Do you want to overwrite config?"), I18nString.get("Loading config"),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

GUIMain.getInstance().setAlwaysOnTop(false);

if (option != JOptionPane.YES_OPTION) {
if (!uiActions.confirmOverwriteConfig()) {

return NanoHTTPD.newFixedLengthResponse(Response.Status.UNAUTHORIZED, MIME_HTML, null);
}
Expand All @@ -137,26 +129,7 @@ public Response serve(IHTTPSession session) {
session.parseBody(map);
String json = map.get("postData");

ConfigDaoHub daoHub = new Gson().fromJson(json, ConfigDaoHub.class);

Database.getInstance().dropConfigs();

for (ListenPort listenPort : daoHub.listenPortList) {

ListenPorts.getInstance().create(listenPort);
}
for (Server server : daoHub.serverList) {

Servers.getInstance().create(server);
}
for (Modification mod : daoHub.modificationList) {

Modifications.getInstance().create(mod);
}
for (SSLPassThrough passThrough : daoHub.sslPassThroughList) {

SSLPassThroughs.getInstance().create(passThrough);
}
settingsWriter.applyFromJson(json);

Response res = NanoHTTPD.newFixedLengthResponse(Response.Status.OK, "application/json",
"{\"status\": \"ok\"}");
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/core/packetproxy/common/ConfigHttpUiActions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2026 DeNA Co., Ltd.
*
* 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 packetproxy.common;

/** リモート設定 HTTP API から必要な GUI 操作。 */
public interface ConfigHttpUiActions {

/**
* 設定タブを表示し、設定を上書きしてよいかユーザーに確認する。
*
* @return 上書きを許可する場合 true
*/
boolean confirmOverwriteConfig();
}
46 changes: 46 additions & 0 deletions src/main/java/core/packetproxy/common/ConfigSettingsWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2026 DeNA Co., Ltd.
*
* 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 packetproxy.common;

import com.google.gson.Gson;
import packetproxy.model.*;

/** リモート設定 JSON を DB に反映する。 */
public class ConfigSettingsWriter {

public void applyFromJson(String json) throws Exception {
var daoHub = new Gson().fromJson(json, ConfigDaoHub.class);

Database.getInstance().dropConfigs();

for (ListenPort listenPort : daoHub.listenPortList) {

ListenPorts.getInstance().create(listenPort);
}
for (Server server : daoHub.serverList) {

Servers.getInstance().create(server);
}
for (Modification mod : daoHub.modificationList) {

Modifications.getInstance().create(mod);
}
for (SSLPassThrough passThrough : daoHub.sslPassThroughList) {

SSLPassThroughs.getInstance().create(passThrough);
}
}
}
4 changes: 3 additions & 1 deletion src/main/java/core/packetproxy/gui/GUIOptionHubServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javax.swing.*;
import org.apache.commons.lang3.RandomStringUtils;
import packetproxy.common.ConfigHttpServer;
import packetproxy.common.ConfigSettingsWriter;
import packetproxy.common.I18nString;
import packetproxy.model.ConfigBoolean;
import packetproxy.model.ConfigString;
Expand Down Expand Up @@ -104,7 +105,8 @@ private void stopServer() {

private void startServer() throws Exception {
String accessToken = new ConfigString("SharingConfigsAccessToken").getString();
this.server = new ConfigHttpServer("localhost", 32349, accessToken);
this.server = new ConfigHttpServer("localhost", 32349, accessToken, new SwingConfigHttpUiActions(),
new ConfigSettingsWriter());
this.server.start();
}

Expand Down
46 changes: 46 additions & 0 deletions src/main/java/core/packetproxy/gui/SwingConfigHttpUiActions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2026 DeNA Co., Ltd.
*
* 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 packetproxy.gui;

import javax.swing.JOptionPane;
import packetproxy.common.ConfigHttpUiActions;
import packetproxy.common.I18nString;

public class SwingConfigHttpUiActions implements ConfigHttpUiActions {

@Override
public boolean confirmOverwriteConfig() {
try {
return confirmOverwriteConfigInternal();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}

private boolean confirmOverwriteConfigInternal() throws Exception {
var main = GUIMain.getInstance();
main.setAlwaysOnTop(true);
try {
main.setVisible(true);
main.getTabbedPane().setSelectedIndex(GUIMain.Panes.OPTIONS.ordinal());
int option = JOptionPane.showConfirmDialog(main, I18nString.get("Do you want to overwrite config?"),
I18nString.get("Loading config"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
return option == JOptionPane.YES_OPTION;
} finally {
main.setAlwaysOnTop(false);
}
}
}
123 changes: 123 additions & 0 deletions src/test/java/packetproxy/common/ConfigHttpServerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2026 DeNA Co., Ltd.
*
* 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 packetproxy.common;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import fi.iki.elonen.NanoHTTPD.IHTTPSession;
import fi.iki.elonen.NanoHTTPD.Method;
import fi.iki.elonen.NanoHTTPD.Response;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

public class ConfigHttpServerTest {

private static final String TOKEN = "secret-token";

private ConfigHttpUiActions uiActions;
private ConfigSettingsWriter settingsWriter;
private ConfigHttpServer server;

@BeforeEach
public void setUp() {
uiActions = mock(ConfigHttpUiActions.class);
settingsWriter = mock(ConfigSettingsWriter.class);
server = new ConfigHttpServer("127.0.0.1", 0, TOKEN, uiActions, settingsWriter);
}

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {"wrong-token"})
public void serve_rejectsUnauthorizedToken(String authorization) throws Exception {
var session = mockSession(Method.POST, "/config", authorization, null);

var response = server.serve(session);

assertEquals(Response.Status.UNAUTHORIZED, response.getStatus());
verify(uiActions, never()).confirmOverwriteConfig();
verify(settingsWriter, never()).applyFromJson(anyString());
}

@Test
public void serve_postConfig_rejectsWhenConfirmDenied() throws Exception {
when(uiActions.confirmOverwriteConfig()).thenReturn(false);
var session = mockSession(Method.POST, "/config", TOKEN, "{\"servers\":[]}");

var response = server.serve(session);

assertEquals(Response.Status.UNAUTHORIZED, response.getStatus());
verify(uiActions).confirmOverwriteConfig();
verify(settingsWriter, never()).applyFromJson(anyString());
}

@Test
public void serve_postConfig_appliesJsonWhenConfirmAccepted() throws Exception {
when(uiActions.confirmOverwriteConfig()).thenReturn(true);
var postData = "{\"servers\":[]}";
var session = mockSession(Method.POST, "/config", TOKEN, postData);

var response = server.serve(session);

assertEquals(Response.Status.OK, response.getStatus());
assertEquals("application/json", response.getMimeType());
assertEquals("{\"status\": \"ok\"}", readBody(response));
verify(uiActions).confirmOverwriteConfig();
verify(settingsWriter).applyFromJson(postData);
}

private static IHTTPSession mockSession(Method method, String uri, String authorization, String postData)
throws Exception {
var session = mock(IHTTPSession.class);
when(session.getMethod()).thenReturn(method);
when(session.getUri()).thenReturn(uri);
Map<String, String> headers = new HashMap<>();
if (authorization != null) {
headers.put("authorization", authorization);
}
when(session.getHeaders()).thenReturn(Collections.unmodifiableMap(headers));
if (postData != null) {
doAnswer(invocation -> {
Map<String, String> map = invocation.getArgument(0);
map.put("postData", postData);
return null;
}).when(session).parseBody(any());
}
return session;
}

private static String readBody(Response response) throws Exception {
InputStream data = response.getData();
if (data == null) {
return null;
}
return new String(data.readAllBytes(), StandardCharsets.UTF_8);
}
}
Loading