1.2.0
This commit is contained in:
commit
6ae2ece6d6
BIN
lib/BungeeCord.jar
Normal file
BIN
lib/BungeeCord.jar
Normal file
Binary file not shown.
28
pom.xml
Normal file
28
pom.xml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.yaohun.guaji.AuGuaJi</groupId>
|
||||
<artifactId>AuMcBot</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>public</id>
|
||||
<url>https://repo.aurora-pixels.com/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
66
src/main/java/com/yaohun/mcbot/McBot.java
Normal file
66
src/main/java/com/yaohun/mcbot/McBot.java
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package com.yaohun.mcbot;
|
||||
|
||||
import com.yaohun.mcbot.client.QQWebSocketClient;
|
||||
import com.yaohun.mcbot.commands.BindAdminCmd;
|
||||
import com.yaohun.mcbot.commands.BindTencentCmd;
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
import com.yaohun.mcbot.data.sql.SQLIO;
|
||||
import com.yaohun.mcbot.listsener.FriendListener;
|
||||
import com.yaohun.mcbot.listsener.GroupListener;
|
||||
import com.yaohun.mcbot.manage.CacheManager;
|
||||
import com.yaohun.mcbot.manage.WssReconnectManager;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class McBot extends Plugin {
|
||||
|
||||
private static McBot instance;
|
||||
|
||||
private static WssReconnectManager wssManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
getLogger().info("[McBot] Start Plugins ...");
|
||||
Config.reloadConfig(this);
|
||||
SQLIO.init(); // 激活MySQL - 数据库的连接
|
||||
|
||||
String serverUri = "ws://127.0.0.1:6050";
|
||||
WssReconnectManager.initialize(this, serverUri);
|
||||
|
||||
QQWebSocketClient client = new QQWebSocketClient(serverUri);
|
||||
WssReconnectManager.setClient(client);
|
||||
client.connect();
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new BindTencentCmd());
|
||||
getProxy().getPluginManager().registerCommand(this, new BindAdminCmd());
|
||||
|
||||
getProxy().getPluginManager().registerListener(this, new GroupListener());
|
||||
getProxy().getPluginManager().registerListener(this, new FriendListener());
|
||||
|
||||
ProxyServer.getInstance().getScheduler().schedule(this, CacheManager::checkingCleanUpData, 1L, 5L, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
SQLIO.closeConnection(); // 关闭MySQL - 数据库的连接
|
||||
getLogger().info("[WebSocket] 插件卸载,准备关闭 WebSocket 通道...");
|
||||
QQWebSocketClient client = WssReconnectManager.getClient();
|
||||
if (client != null && client.isOpen()) {
|
||||
try {
|
||||
client.close(); // 发送关闭帧 + 关闭资源
|
||||
getLogger().info("[WebSocket] WebSocket 通道已成功关闭。");
|
||||
} catch (Exception e) {
|
||||
getLogger().warning("[WebSocket] 关闭通道时出现异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static McBot inst() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
92
src/main/java/com/yaohun/mcbot/MessageForwarder.java
Normal file
92
src/main/java/com/yaohun/mcbot/MessageForwarder.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package com.yaohun.mcbot;
|
||||
|
||||
|
||||
import com.yaohun.mcbot.data.BotFriend;
|
||||
import com.yaohun.mcbot.data.BotGroup;
|
||||
import com.yaohun.mcbot.event.BotFriendMessageEvent;
|
||||
import com.yaohun.mcbot.event.BotGroupMessageEvent;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class MessageForwarder {
|
||||
|
||||
public static void friendsAnalyze(String json){
|
||||
try {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
// 检测类型是否是 “好友消息” 若不是则拦截返回
|
||||
if (!"private".equals(obj.optString("message_type"))) return;
|
||||
// 获取机器人的QQ号
|
||||
long botId = obj.optLong("self_id");
|
||||
// 获取聊天信息内容 若没有消息则返回
|
||||
JSONArray messageArray = obj.optJSONArray("message");
|
||||
if (messageArray == null) return;
|
||||
// 将聊天信息内容 builder
|
||||
StringBuilder finalMessage = new StringBuilder();
|
||||
for (int i = 0; i < messageArray.length(); i++) {
|
||||
JSONObject segment = messageArray.getJSONObject(i);
|
||||
String type = segment.optString("type");
|
||||
if ("text".equals(type)) {
|
||||
String text = segment.getJSONObject("data").optString("text");
|
||||
finalMessage.append(text);
|
||||
}
|
||||
// 可以在这里加对 image、face 等其他类型的支持
|
||||
}
|
||||
String message = finalMessage.toString();
|
||||
if(message.isEmpty()) return;
|
||||
// 解析发送信息的人
|
||||
JSONObject sender = obj.optJSONObject("sender");
|
||||
if (sender == null) return;
|
||||
// 获取信息
|
||||
long senderId = sender.optLong("user_id"); // QQ号
|
||||
String nickname = sender.optString("nickname"); // QQ昵称
|
||||
BotFriendMessageEvent event = new BotFriendMessageEvent(botId,senderId,nickname,message,new BotFriend(senderId));
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(event);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void groupAnalyze(String json) {
|
||||
try {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
// 检测类型是否是 “群消息” 若不是则拦截返回
|
||||
if (!"group".equals(obj.optString("message_type"))) return;
|
||||
// 获取机器人的QQ号
|
||||
long botId = obj.optLong("self_id");
|
||||
long groupId = obj.optLong("group_id");
|
||||
// 获取聊天信息内容 若没有消息则返回
|
||||
JSONArray messageArray = obj.optJSONArray("message");
|
||||
if (messageArray == null) return;
|
||||
// 将聊天信息内容 builder
|
||||
StringBuilder finalMessage = new StringBuilder();
|
||||
for (int i = 0; i < messageArray.length(); i++) {
|
||||
JSONObject segment = messageArray.getJSONObject(i);
|
||||
String type = segment.optString("type");
|
||||
if ("text".equals(type)) {
|
||||
String text = segment.getJSONObject("data").optString("text");
|
||||
finalMessage.append(text);
|
||||
}
|
||||
// 可以在这里加对 image、face 等其他类型的支持
|
||||
}
|
||||
String message = finalMessage.toString();
|
||||
if(message.isEmpty()) return;
|
||||
// 解析发送信息的人
|
||||
JSONObject sender = obj.optJSONObject("sender");
|
||||
if (sender == null) return;
|
||||
// 获取信息
|
||||
long senderId = sender.optLong("user_id"); // QQ号
|
||||
String nickname = sender.optString("nickname"); // QQ昵称
|
||||
String card = sender.optString("card"); // 群名片
|
||||
String role = sender.optString("role"); // 群中职务
|
||||
// 优先显示群名片(如果存在),否则用昵称
|
||||
String displayName = (card != null && !card.isEmpty()) ? card : nickname;
|
||||
|
||||
BotGroupMessageEvent event = new BotGroupMessageEvent(botId,groupId,senderId,displayName,message,new BotGroup(groupId));
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(event);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/yaohun/mcbot/api/McBotAPI.java
Normal file
16
src/main/java/com/yaohun/mcbot/api/McBotAPI.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.yaohun.mcbot.api;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.data.BotFriend;
|
||||
import com.yaohun.mcbot.data.BotGroup;
|
||||
|
||||
public class McBotAPI {
|
||||
|
||||
public static BotFriend getFriend(long senderID){
|
||||
return new BotFriend(senderID);
|
||||
}
|
||||
|
||||
public static BotGroup getGroup(long groupID){
|
||||
return new BotGroup(groupID);
|
||||
}
|
||||
}
|
||||
53
src/main/java/com/yaohun/mcbot/client/QQWebSocketClient.java
Normal file
53
src/main/java/com/yaohun/mcbot/client/QQWebSocketClient.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package com.yaohun.mcbot.client;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.MessageForwarder;
|
||||
import com.yaohun.mcbot.manage.WssReconnectManager;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class QQWebSocketClient extends WebSocketClient {
|
||||
|
||||
public QQWebSocketClient( String serverUri) {
|
||||
super(URI.create(serverUri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshake) {
|
||||
McBot.inst().getLogger().info("[WebSocket-Link] connectionSuccessfully!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
JSONObject json = new JSONObject(message);
|
||||
if ("group".contains(json.optString("message_type"))){
|
||||
MessageForwarder.groupAnalyze(message);
|
||||
// System.out.println("[WebSocket-Group] "+json.getString("raw_message"));
|
||||
} else if ("private".contains(json.optString("message_type"))){
|
||||
MessageForwarder.friendsAnalyze(message);
|
||||
// System.out.println("[WebSocket-Friends] "+json.getString("raw_message"));
|
||||
} else {
|
||||
System.out.println("[WebSocket-MSG] "+json);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
WssReconnectManager.tryReconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
WssReconnectManager.tryReconnect();
|
||||
}
|
||||
|
||||
}
|
||||
75
src/main/java/com/yaohun/mcbot/commands/BindAdminCmd.java
Normal file
75
src/main/java/com/yaohun/mcbot/commands/BindAdminCmd.java
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package com.yaohun.mcbot.commands;
|
||||
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
import com.yaohun.mcbot.data.sql.SQLIO;
|
||||
import com.yaohun.mcbot.manage.CacheManager;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BindAdminCmd extends Command {
|
||||
|
||||
|
||||
private static String prefix;
|
||||
|
||||
public BindAdminCmd() {
|
||||
super("bindadmin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if(!sender.hasPermission("bungeecord.*")){return;}
|
||||
if(args.length == 0){
|
||||
sender.sendMessage("§e------- ======= §6妖魂的MC机器人 §e======= -------");
|
||||
sender.sendMessage("§2/bindadmin clear §f- §2清理所有缓存");
|
||||
sender.sendMessage("§2/bindadmin change §e[玩家名] §b<企鹅号> §f- §2修改绑定");
|
||||
sender.sendMessage("§2/bindadmin group §e[群号] §f- §2新增/删除检测群");
|
||||
sender.sendMessage("§e------- ======= §6妖魂的MC机器人 §e======= -------");
|
||||
return;
|
||||
}
|
||||
if(args.length == 1 && args[0].equalsIgnoreCase("clear")){
|
||||
CacheManager.codeDataMap.clear();
|
||||
CacheManager.randomCodeMap.clear();
|
||||
sender.sendMessage("[Bot管理] 所有缓存数据已被清理");
|
||||
return;
|
||||
}
|
||||
if(args[0].equalsIgnoreCase("change")){
|
||||
if(args.length == 1){
|
||||
sender.sendMessage("[Bot管理] 请填入正确的玩家名.");
|
||||
return;
|
||||
}
|
||||
if(args.length == 2){
|
||||
sender.sendMessage("[Bot管理] 请填入正确的企鹅号.");
|
||||
return;
|
||||
}
|
||||
String playerName = args[1];
|
||||
String senderID = args[2];
|
||||
SQLIO.saveGroup(playerName, senderID);
|
||||
sender.sendMessage("[Bot管理] 已更改 "+playerName+" 的绑定为 <"+senderID+">");
|
||||
return;
|
||||
}
|
||||
if(args[0].equalsIgnoreCase("group")){
|
||||
if(args.length == 1){
|
||||
sender.sendMessage("[Bot管理] 请填写群号.");
|
||||
return;
|
||||
}
|
||||
long groupID = Long.parseLong(args[1]);
|
||||
List<Long> groupAccountList = Config.BOT_GroupAccounts;
|
||||
if(groupAccountList.contains(groupID)){
|
||||
Config.BOT_GroupAccounts.remove(groupID);
|
||||
sender.sendMessage("[Bot管理] 已移除检测群: "+groupID);
|
||||
} else {
|
||||
Config.BOT_GroupAccounts.add(groupID);
|
||||
sender.sendMessage("[Bot管理] 已添加检测群: "+groupID);
|
||||
}
|
||||
Config.saveBOT_GroupAccounts();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/main/java/com/yaohun/mcbot/commands/BindTencentCmd.java
Normal file
91
src/main/java/com/yaohun/mcbot/commands/BindTencentCmd.java
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package com.yaohun.mcbot.commands;
|
||||
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
import com.yaohun.mcbot.data.sql.SQLIO;
|
||||
import com.yaohun.mcbot.manage.CacheManager;
|
||||
import gnu.trove.stack.TLongStack;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class BindTencentCmd extends Command {
|
||||
|
||||
private static String prefix;
|
||||
|
||||
public BindTencentCmd() {
|
||||
super("bind");
|
||||
prefix = Config.getMessage("prefix");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
// 首先判断输入此命令的是否是玩家
|
||||
if(sender instanceof ProxiedPlayer) {
|
||||
if (args.length == 2) {
|
||||
String tencentQQ = args[0].replaceAll("\\[|\\]", "");
|
||||
String tencentQQRepeat = args[1].replaceAll("\\[|\\]", "");
|
||||
ProxiedPlayer player = (ProxiedPlayer) sender;
|
||||
if (!tencentQQ.equalsIgnoreCase(tencentQQRepeat)) {
|
||||
String msg = Config.getMessage("differentTwice");
|
||||
player.sendMessage(prefix+msg);
|
||||
return;
|
||||
}
|
||||
String userName = player.getName();
|
||||
String cacheCode = CacheManager.getRandomCodeData(userName);
|
||||
if(cacheCode != null) {
|
||||
sender.sendMessage(" ");
|
||||
sender.sendMessage(" ");
|
||||
if(!Config.Group_Info.isEmpty()) {
|
||||
sender.sendMessage(Config.Group_Info);
|
||||
}
|
||||
SendCopyMessage(player,cacheCode);
|
||||
sender.sendMessage(" ");
|
||||
return;
|
||||
}
|
||||
// 判断玩家是否已绑定QQ号 若已绑定则拦截数据
|
||||
String userID = SQLIO.getUserID(userName);
|
||||
if (userID != null) {
|
||||
String message = Config.getMessage("alreadyBindUserID");
|
||||
player.sendMessage(prefix + message.replace("%qq%", userID));
|
||||
return;
|
||||
}
|
||||
// 判断QQ是否已绑定过账号 若已绑定则连接数据
|
||||
String user = SQLIO.getUserName(tencentQQ);
|
||||
if (user != null) {
|
||||
String message = Config.getMessage("alreadyBindUserName");
|
||||
player.sendMessage(prefix + message.replace("%user%", user));
|
||||
return;
|
||||
}
|
||||
// 获取绑定验证码发送给玩家
|
||||
String randomCode = Config.getRandomString();
|
||||
CacheManager.setRandomCodeData(userName, randomCode); // 将验证码存入缓存
|
||||
CacheManager.setCodeDataMap(randomCode,new CacheManager.CodeData(userName,randomCode,tencentQQ));
|
||||
sender.sendMessage(" ");
|
||||
sender.sendMessage(" ");
|
||||
if(!Config.Group_Info.isEmpty()) {
|
||||
sender.sendMessage(Config.Group_Info);
|
||||
}
|
||||
SendCopyMessage(player, randomCode);
|
||||
sender.sendMessage(" ");
|
||||
} else {
|
||||
String msg = Config.getMessage("correctOperate");
|
||||
sender.sendMessage(prefix+msg);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("§c此命令仅限以玩家身份执行.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SendCopyMessage(ProxiedPlayer player,String bind_code) {
|
||||
String message = prefix+Config.getMessage("verificationCode").replace("%code%",bind_code);
|
||||
TextComponent component = new TextComponent(message);
|
||||
System.out.print("玩家: "+player.getName()+" randomCode: "+bind_code);
|
||||
component.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/绑定 "+bind_code));
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§e§l/绑定 "+bind_code).create()));
|
||||
player.sendMessage(component);
|
||||
}
|
||||
}
|
||||
126
src/main/java/com/yaohun/mcbot/config/Config.java
Normal file
126
src/main/java/com/yaohun/mcbot/config/Config.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package com.yaohun.mcbot.config;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class Config {
|
||||
|
||||
private static Configuration config;
|
||||
public static String SQL_Host;
|
||||
public static String SQL_Port;
|
||||
public static String SQL_Database;
|
||||
public static String SQL_Users;
|
||||
public static String SQL_Password;
|
||||
public static List<Long> BOT_GroupAccounts = new ArrayList<>();
|
||||
public static String Group_Info = "";
|
||||
private static HashMap<String,String> messageMap = new HashMap<>();
|
||||
|
||||
public static void reloadConfig(McBot plugin) {
|
||||
if(!plugin.getDataFolder().exists()) {
|
||||
plugin.getDataFolder().mkdir();
|
||||
}
|
||||
File file = new File(plugin.getDataFolder(), "config.yml");
|
||||
if(!file.exists()) {
|
||||
try {
|
||||
file.createNewFile();
|
||||
try (InputStream is = plugin.getResourceAsStream("config.yml");
|
||||
OutputStream os = Files.newOutputStream(file.toPath())) {
|
||||
ByteStreams.copy(is, os);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to create configuration file", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
ConfigurationProvider cfg = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
config = cfg.load(file);
|
||||
loadMySQLData();
|
||||
loadConfigData();
|
||||
loadMessageData();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveBOT_GroupAccounts() {
|
||||
List<String > groupAccountList = new ArrayList<>();
|
||||
for (Long l : BOT_GroupAccounts) {
|
||||
groupAccountList.add(l.toString());
|
||||
}
|
||||
File file = new File(McBot.inst().getDataFolder(), "config.yml");
|
||||
// 转换 List<Long> 为 List<String>,避免 YAML 不兼容 Long 处理
|
||||
config.set("Group-Numbers", groupAccountList);
|
||||
try {
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(config, file);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadMySQLData(){
|
||||
SQL_Host = config.getString("MYSQL.Host");
|
||||
SQL_Port = config.getString("MYSQL.Port");
|
||||
SQL_Database = config.getString("MYSQL.Database");
|
||||
SQL_Users = config.getString("MYSQL.Users");
|
||||
SQL_Password = config.getString("MYSQL.Password");
|
||||
}
|
||||
|
||||
private static void loadConfigData(){
|
||||
List<String> stringList = config.getStringList("Group-Numbers");
|
||||
System.out.println("[AuBot] 监听QQ群: ");
|
||||
for (String value : stringList) {
|
||||
BOT_GroupAccounts.add(Long.parseLong(value));
|
||||
System.out.println("- "+value);
|
||||
}
|
||||
Group_Info = config.getString("Group_Info","").replace("&","§");
|
||||
}
|
||||
|
||||
private static void loadMessageData(){
|
||||
if(config.getString("Message") == null) return;
|
||||
for (String key : config.getSection("Message").getKeys()){
|
||||
String value = config.getString("Message." + key);
|
||||
messageMap.put(key, value.replace("&","§"));
|
||||
}
|
||||
System.out.println("[AuBot] 语言参数 "+messageMap.size()+"个");
|
||||
}
|
||||
|
||||
public static String getMessage(String key){
|
||||
if(messageMap.containsKey(key)){
|
||||
return messageMap.get(key);
|
||||
}
|
||||
return "缺少节点."+key;
|
||||
}
|
||||
|
||||
public static String getRandomString() {
|
||||
String str = "CEFGHIJKLMNPQTXY34679";
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int number = random.nextInt(21);
|
||||
sb.append(str.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String extractBindCode(String message) {
|
||||
return message
|
||||
.replace("/绑定 ", "")
|
||||
.replace(" ", "")
|
||||
.toUpperCase()
|
||||
.replaceAll("[^A-Z0-9]", "");
|
||||
}
|
||||
|
||||
}
|
||||
27
src/main/java/com/yaohun/mcbot/data/BotFriend.java
Normal file
27
src/main/java/com/yaohun/mcbot/data/BotFriend.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.yaohun.mcbot.data;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.manage.WssReconnectManager;
|
||||
|
||||
public class BotFriend {
|
||||
|
||||
private long userId;
|
||||
|
||||
public BotFriend(long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public void sendMessage(String message){
|
||||
String escapedMessage = message
|
||||
.replace("\\", "\\\\") // 转义反斜线
|
||||
.replace("\"", "\\\"") // 转义引号
|
||||
.replace("\n", "\\n") // 转义换行符
|
||||
.replace("\r", "\\r"); // 转义回车符(保险起见)
|
||||
|
||||
String payload = String.format(
|
||||
"{\"action\":\"send_private_msg\",\"params\":{\"user_id\":%d,\"message\":\"%s\"}}",
|
||||
userId, escapedMessage
|
||||
);
|
||||
WssReconnectManager.getClient().send(payload);
|
||||
}
|
||||
}
|
||||
27
src/main/java/com/yaohun/mcbot/data/BotGroup.java
Normal file
27
src/main/java/com/yaohun/mcbot/data/BotGroup.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.yaohun.mcbot.data;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.manage.WssReconnectManager;
|
||||
|
||||
public class BotGroup {
|
||||
|
||||
private long groupId;
|
||||
|
||||
public BotGroup(long groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public void sendMessage(String message){
|
||||
String escapedMessage = message
|
||||
.replace("\\", "\\\\") // 转义反斜线
|
||||
.replace("\"", "\\\"") // 转义引号
|
||||
.replace("\n", "\\n") // 转义换行符
|
||||
.replace("\r", "\\r"); // 转义回车符(保险起见)
|
||||
|
||||
String payload = String.format(
|
||||
"{\"action\":\"send_group_msg\",\"params\":{\"group_id\":%d,\"message\":\"%s\"}}",
|
||||
groupId, escapedMessage
|
||||
);
|
||||
WssReconnectManager.getClient().send(payload);
|
||||
}
|
||||
}
|
||||
179
src/main/java/com/yaohun/mcbot/data/sql/SQLIO.java
Normal file
179
src/main/java/com/yaohun/mcbot/data/sql/SQLIO.java
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
package com.yaohun.mcbot.data.sql;
|
||||
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class SQLIO {
|
||||
|
||||
private static String host;
|
||||
private static int port;
|
||||
private static String database;
|
||||
private static String username;
|
||||
private static String password;
|
||||
private static String tableName;
|
||||
|
||||
private static Connection connection;
|
||||
|
||||
public static void init(){
|
||||
host = Config.SQL_Host;
|
||||
port = Integer.parseInt(Config.SQL_Port);
|
||||
database = Config.SQL_Database;
|
||||
username = Config.SQL_Users;
|
||||
password = Config.SQL_Password;
|
||||
tableName = "binddata";
|
||||
try {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
getConnection();
|
||||
initTable();
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initTable() {
|
||||
try {
|
||||
Statement stat = getConnection().createStatement();
|
||||
stat.execute(
|
||||
"CREATE TABLE IF NOT EXISTS "+tableName+" (" +
|
||||
" username VARCHAR(64) NOT NULL," +
|
||||
" userid VARCHAR(64) NOT NULL," +
|
||||
" PRIMARY KEY (username)" +
|
||||
")"
|
||||
);
|
||||
stat.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasUserNameExit(String userName) {
|
||||
try {
|
||||
try (PreparedStatement ps = getConnection().prepareStatement("SELECT `userid` FROM "+tableName+" WHERE `username` = ? LIMIT 1")) {
|
||||
ps.setString(1, userName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasUserIDExit(String userID) {
|
||||
try {
|
||||
String sql = "SELECT `username` FROM " + tableName + " WHERE `userid` = ? LIMIT 1";
|
||||
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, userID);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
return rs.next(); // 查询到了,说明存在
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveGroup(String userName, String userID) {
|
||||
if (!SQLIO.hasUserNameExit(userName)) {
|
||||
insertGroup(userName, userID);
|
||||
} else {
|
||||
updateGroup(userName, userID);
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertGroup(String userName, String userID) {
|
||||
try {
|
||||
String sql = "INSERT INTO "+tableName+"(username, userid) VALUES(?,?);";
|
||||
try (PreparedStatement ps = SQLIO.getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, userName);
|
||||
ps.setString(2 , userID);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateGroup(String userName, String userID) {
|
||||
try {
|
||||
String sql = "UPDATE `"+tableName+"` SET `userid` = ? WHERE `username` = ? LIMIT 1";
|
||||
try (PreparedStatement ps = SQLIO.getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, userID);
|
||||
ps.setString(2, userName);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUserID(String userName) {
|
||||
try {
|
||||
String sql = "SELECT `userid` FROM `"+tableName+"` WHERE `username` = ? LIMIT 1";
|
||||
try (PreparedStatement ps = SQLIO.getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, userName);
|
||||
try (ResultSet resultSet = ps.executeQuery()) {
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getString(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUserName(String userID) {
|
||||
try {
|
||||
String sql = "SELECT `username` FROM `" + tableName + "` WHERE `userid` = ? LIMIT 1";
|
||||
try (PreparedStatement ps = SQLIO.getConnection().prepareStatement(sql)) {
|
||||
ps.setString(1, userID);
|
||||
try (ResultSet resultSet = ps.executeQuery()) {
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getString(1); // 返回 username
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Connection getConnection() {
|
||||
try {
|
||||
if (connection == null) {
|
||||
String url = "jdbc:mysql://" + host + ":" + port + "/" + database
|
||||
+ "?autoReconnect=true"
|
||||
+ "&failOverReadOnly=false"
|
||||
+ "&allowMultiQueries=true"
|
||||
+ "&useSSL=false"
|
||||
+ "&useUnicode=true"
|
||||
+ "&characterEncoding=utf8";
|
||||
connection = DriverManager.getConnection(url, username, password);
|
||||
System.out.println("[AuBot] 数据库: "+host+":"+port+"/"+database+" (已正常连接)");
|
||||
return connection;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("[AuBot] 数据库: "+host+":"+port+"/"+database+" (连接失败)");
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static void closeConnection() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.yaohun.mcbot.event;
|
||||
|
||||
import com.yaohun.mcbot.data.BotFriend;
|
||||
import com.yaohun.mcbot.data.BotGroup;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BotFriendMessageEvent extends Event {
|
||||
|
||||
private long botId;
|
||||
|
||||
private long senderId;
|
||||
|
||||
private String senderName;
|
||||
|
||||
private String message;
|
||||
|
||||
private BotFriend botFriend;
|
||||
|
||||
public BotFriendMessageEvent(long botId, long senderId, String senderName, String message, BotFriend botFriend) {
|
||||
this.botId = botId;
|
||||
this.senderId = senderId;
|
||||
this.senderName = senderName;
|
||||
this.message = message;
|
||||
this.botFriend = botFriend;
|
||||
}
|
||||
|
||||
public long getBotId() {
|
||||
return botId;
|
||||
}
|
||||
|
||||
public long getSenderId() {
|
||||
return senderId;
|
||||
}
|
||||
|
||||
public String getSenderName() {
|
||||
return senderName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public BotFriend getBotFriend() {
|
||||
return botFriend;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.yaohun.mcbot.event;
|
||||
|
||||
import com.yaohun.mcbot.data.BotGroup;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BotGroupMessageEvent extends Event {
|
||||
|
||||
private long botId;
|
||||
|
||||
private long groupId;
|
||||
|
||||
private long senderId;
|
||||
|
||||
private String senderName;
|
||||
|
||||
private String message;
|
||||
|
||||
private BotGroup botGroup;
|
||||
|
||||
public BotGroupMessageEvent(long botId, long groupId, long senderId, String senderName, String message, BotGroup botGroup) {
|
||||
this.botId = botId;
|
||||
this.groupId = groupId;
|
||||
this.senderId = senderId;
|
||||
this.senderName = senderName;
|
||||
this.message = message;
|
||||
this.botGroup = botGroup;
|
||||
}
|
||||
|
||||
public long getBotId() {
|
||||
return botId;
|
||||
}
|
||||
|
||||
public long getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public long getSenderId() {
|
||||
return senderId;
|
||||
}
|
||||
|
||||
public String getSenderName() {
|
||||
return senderName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public BotGroup getBotGroup() {
|
||||
return botGroup;
|
||||
}
|
||||
}
|
||||
84
src/main/java/com/yaohun/mcbot/listsener/FriendListener.java
Normal file
84
src/main/java/com/yaohun/mcbot/listsener/FriendListener.java
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package com.yaohun.mcbot.listsener;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.api.McBotAPI;
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
import com.yaohun.mcbot.data.sql.SQLIO;
|
||||
import com.yaohun.mcbot.event.BotFriendMessageEvent;
|
||||
import com.yaohun.mcbot.event.BotGroupMessageEvent;
|
||||
import com.yaohun.mcbot.util.CDTimeAPI;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FriendListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onGroupChat(BotFriendMessageEvent e) {
|
||||
long senderId = e.getSenderId();
|
||||
if(CDTimeAPI.isCD(senderId,"groupBotCd")){
|
||||
return;
|
||||
}
|
||||
CDTimeAPI.addPlayerCD(senderId,"groupBotCd",1000 * 5);
|
||||
String message = e.getMessage();
|
||||
if(message.contains("/功能") || message.contains("/菜单")) {
|
||||
String to_message = "/查询 - 查看绑定信息\n" +
|
||||
"/在线 - 查看当前在线人数\n" +
|
||||
"/延迟 [玩家] - 网络延迟查看\n" +
|
||||
"/强制下线 - 让账号在游戏中强制退出";
|
||||
e.getBotFriend().sendMessage(to_message);
|
||||
}else if(e.getMessage().equalsIgnoreCase("/在线")) {
|
||||
int onlineplayer = BungeeCord.getInstance().getOnlineCount();
|
||||
String msg = Config.getMessage("checkServerOnline").replace("%count%",String.valueOf(onlineplayer));
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
}else if(message.contains("/查询")) {
|
||||
// 获取这个发送者的QQ号
|
||||
String userName = SQLIO.getUserName(String.valueOf(e.getSenderId()));
|
||||
if(userName == null){
|
||||
String msg = Config.getMessage("notBoundYet");
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
String msg = Config.getMessage("querySuccessful");
|
||||
e.getBotFriend().sendMessage(msg.replace("%user%", userName));
|
||||
}else if(e.getMessage().contains("/延迟 ")){
|
||||
// 获取玩家名字
|
||||
String playName = e.getMessage().replace("/延迟 ","").replace(" ","");
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playName);
|
||||
if (player == null) {
|
||||
String msg = Config.getMessage("offlinePlayer").replace("%user%", playName);
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
String msg = Config.getMessage("checkPlayerPing").replace("%user%", playName);
|
||||
msg = msg.replace("%ping%",String.valueOf(player.getPing()));
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
}else if(e.getMessage().contains("/强制下线")){
|
||||
// 获取这个发送者的QQ号
|
||||
String userName = SQLIO.getUserName(String.valueOf(e.getSenderId()));
|
||||
if(userName == null){
|
||||
String msg = Config.getMessage("unboundAccount");
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(userName);
|
||||
if (player == null) {
|
||||
String msg = Config.getMessage("offlinePlayerError").replace("%user%", userName);
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
ProxyServer.getInstance().getScheduler().schedule(McBot.inst(),
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
player.disconnect("§c号主已进行强制下线");
|
||||
String msg = Config.getMessage("kickOnlinPlayer").replace("%user%",userName);
|
||||
e.getBotFriend().sendMessage(msg);
|
||||
}
|
||||
}, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/main/java/com/yaohun/mcbot/listsener/GroupListener.java
Normal file
105
src/main/java/com/yaohun/mcbot/listsener/GroupListener.java
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package com.yaohun.mcbot.listsener;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.api.McBotAPI;
|
||||
import com.yaohun.mcbot.config.Config;
|
||||
import com.yaohun.mcbot.data.BotFriend;
|
||||
import com.yaohun.mcbot.data.sql.SQLIO;
|
||||
import com.yaohun.mcbot.event.BotGroupMessageEvent;
|
||||
import com.yaohun.mcbot.manage.CacheManager;
|
||||
import com.yaohun.mcbot.util.CDTimeAPI;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GroupListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onGroupChat(BotGroupMessageEvent e) {
|
||||
if (Config.BOT_GroupAccounts.contains(e.getGroupId())) {
|
||||
long senderId = e.getSenderId();
|
||||
String message = e.getMessage();
|
||||
if (message.contains("/在线")) {
|
||||
if (CDTimeAPI.isCD(senderId, "groupBotCd")) {
|
||||
return;
|
||||
}
|
||||
CDTimeAPI.addPlayerCD(senderId, "groupBotCd", 1000 * 10);
|
||||
int onlineplayer = BungeeCord.getInstance().getOnlineCount();
|
||||
String msg = Config.getMessage("checkServerOnline").replace("%count%", String.valueOf(onlineplayer));
|
||||
e.getBotGroup().sendMessage(msg);
|
||||
} else if (message.contains("/查询")) {
|
||||
if (CDTimeAPI.isCD(senderId, "groupBotCd")) {
|
||||
return;
|
||||
}
|
||||
CDTimeAPI.addPlayerCD(senderId, "groupBotCd", 1000 * 10);
|
||||
// 获取这个发送者的QQ号
|
||||
String userName = SQLIO.getUserName(String.valueOf(e.getSenderId()));
|
||||
if (userName == null) {
|
||||
String msg = Config.getMessage("notBoundYet");
|
||||
e.getBotGroup().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
String msg = Config.getMessage("querySuccessful");
|
||||
e.getBotGroup().sendMessage(msg.replace("%user%", userName));
|
||||
} else if (message.contains("/绑定 ")) {
|
||||
if (CDTimeAPI.isCD(senderId, "groupBotCd")) {
|
||||
return;
|
||||
}
|
||||
CDTimeAPI.addPlayerCD(senderId, "groupBotCd", 1000 * 5);
|
||||
String code = Config.extractBindCode(message);
|
||||
// 检测这个验证码是否存在缓存数据
|
||||
CacheManager.CodeData codeData = CacheManager.getCodeData(code);
|
||||
if (codeData == null) {
|
||||
String msg = Config.getMessage("codeError").replace("@{nickName}", "[CQ:at,qq=" + e.getSenderId() + ",name=@" + e.getSenderName() + "]");
|
||||
e.getBotGroup().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
// 判断缓存数据中的QQ和这个发送验证码的QQ是否为同一个
|
||||
String snederID = String.valueOf(e.getSenderId());
|
||||
if (!codeData.getTencentQQ().equals(snederID)) {
|
||||
String msg = Config.getMessage("codeExpired").replace("@{nickName}", "[CQ:at,qq=" + e.getSenderId() + ",name=@" + e.getSenderName() + "]");
|
||||
e.getBotGroup().sendMessage(msg);
|
||||
return;
|
||||
}
|
||||
// 判断这个QQ号是否已绑定游戏账号
|
||||
String userName = SQLIO.getUserName(snederID);
|
||||
if (userName != null) {
|
||||
String msg = Config.getMessage("alreadyGroupBindUserName").replace("@{nickName}", "[CQ:at,qq=" + e.getSenderId() + ",name=@" + e.getSenderName() + "]");
|
||||
e.getBotGroup().sendMessage(msg.replace("%user%", userName));
|
||||
return;
|
||||
}
|
||||
// 判断这个游戏账号是否已绑定其他QQ号
|
||||
String playerName = codeData.getUserName();
|
||||
String userID = SQLIO.getUserID(playerName);
|
||||
if (userID != null) {
|
||||
String msg = Config.getMessage("alreadyGroupBindUserID").replace("@{nickName}", "[CQ:at,qq=" + e.getSenderId() + ",name=@" + e.getSenderName() + "]");
|
||||
e.getBotGroup().sendMessage(msg.replace("%qq%", userID));
|
||||
return;
|
||||
}
|
||||
// 删除缓存数据
|
||||
CacheManager.cleanUpTheCache(playerName, code);
|
||||
ProxyServer.getInstance().getScheduler().schedule(McBot.inst(),
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
//设置数据库数据
|
||||
SQLIO.saveGroup(playerName, snederID);
|
||||
// 向群里发送绑定成功消息
|
||||
String msg1 = Config.getMessage("botBindingSuccessful").replace("@{nickName}", "[CQ:at,qq=" + e.getSenderId() + ",name=@" + e.getSenderName() + "]");
|
||||
e.getBotGroup().sendMessage(msg1.replace("%user%", playerName));
|
||||
// 向游戏发送绑定成功消息
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerName);
|
||||
if (player != null) {
|
||||
String msg2 = Config.getMessage("playerBindingSuccessful").replace("%qq%", String.valueOf(senderId));
|
||||
player.sendMessage(Config.getMessage("prefix") + msg2);
|
||||
}
|
||||
}
|
||||
}, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
82
src/main/java/com/yaohun/mcbot/manage/CacheManager.java
Normal file
82
src/main/java/com/yaohun/mcbot/manage/CacheManager.java
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package com.yaohun.mcbot.manage;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class CacheManager {
|
||||
|
||||
public static HashMap<String,CodeData> codeDataMap = new HashMap<>();
|
||||
public static HashMap<String ,String> randomCodeMap = new HashMap<>();
|
||||
|
||||
public static void setRandomCodeData(String userName,String code){
|
||||
randomCodeMap.put(userName,code);
|
||||
}
|
||||
|
||||
public static String getRandomCodeData(String userName){
|
||||
if(randomCodeMap.containsKey(userName)){
|
||||
return randomCodeMap.get(userName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void setCodeDataMap(String randomCode,CacheManager.CodeData codeData){
|
||||
codeDataMap.put(randomCode,codeData);
|
||||
}
|
||||
|
||||
public static CodeData getCodeData(String randomCode){
|
||||
if(codeDataMap.containsKey(randomCode)){
|
||||
return codeDataMap.get(randomCode);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void cleanUpTheCache(String playerName,String randomCode){
|
||||
codeDataMap.remove(playerName);
|
||||
randomCodeMap.remove(randomCode);
|
||||
}
|
||||
|
||||
public static void checkingCleanUpData(){
|
||||
if(codeDataMap.isEmpty()){
|
||||
return;
|
||||
}
|
||||
codeDataMap.clear();
|
||||
McBot.inst().getLogger().info("[McBot] 定时清理缓存池...");
|
||||
}
|
||||
|
||||
public static class CodeData {
|
||||
private String randomCode;
|
||||
private String userName;
|
||||
private String tencentQQ;
|
||||
private long recordingTime;
|
||||
|
||||
public CodeData(String userName, String randomCode, String tencentQQ) {
|
||||
this.userName = userName;
|
||||
this.randomCode = randomCode;
|
||||
this.tencentQQ = tencentQQ;
|
||||
this.recordingTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public String getRandomCode() {
|
||||
return randomCode;
|
||||
}
|
||||
|
||||
public String getTencentQQ() {
|
||||
return tencentQQ;
|
||||
}
|
||||
|
||||
public boolean isRecordingTime() {
|
||||
long nowTime = System.currentTimeMillis();
|
||||
if(nowTime - recordingTime > (1000 * 120)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.yaohun.mcbot.manage;
|
||||
|
||||
import com.yaohun.mcbot.McBot;
|
||||
import com.yaohun.mcbot.client.QQWebSocketClient;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WssReconnectManager {
|
||||
|
||||
private static final int MAX_RETRY = 5;
|
||||
private static final long RETRY_DELAY_SECONDS = 15;
|
||||
|
||||
private static boolean reconnecting = false;
|
||||
|
||||
private static int retryCount = 0;
|
||||
|
||||
private static QQWebSocketClient currentClient;
|
||||
private static String serverUri;
|
||||
private static McBot plugin;
|
||||
|
||||
public static void initialize(McBot pluginInstance, String uri) {
|
||||
plugin = pluginInstance;
|
||||
serverUri = uri;
|
||||
}
|
||||
|
||||
public static void setClient(QQWebSocketClient client) {
|
||||
currentClient = client;
|
||||
}
|
||||
|
||||
public static QQWebSocketClient getClient() {
|
||||
return currentClient;
|
||||
}
|
||||
|
||||
public static void tryReconnect() {
|
||||
if (reconnecting) return;
|
||||
|
||||
if (currentClient != null && currentClient.isOpen()) return;
|
||||
|
||||
if (retryCount >= MAX_RETRY) {
|
||||
plugin.getLogger().severe("[Bot-WebSocket] 已达到最大重连次数,停止尝试");
|
||||
return;
|
||||
}
|
||||
|
||||
reconnecting = true;
|
||||
retryCount++;
|
||||
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
|
||||
try {
|
||||
// 关闭旧连接
|
||||
if (currentClient != null && currentClient.isOpen()) {
|
||||
plugin.getLogger().info("[Bot-WebSocket] 关闭旧连接...");
|
||||
currentClient.close();
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[Bot-WebSocket] 重新连接尝试 第<" + retryCount + ">次 ...");
|
||||
QQWebSocketClient newClient = new QQWebSocketClient(serverUri);
|
||||
setClient(newClient);
|
||||
newClient.connect();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().severe("[Bot-WebSocket] 重连失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
reconnecting = false;
|
||||
}
|
||||
}, RETRY_DELAY_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static void resetRetry() {
|
||||
retryCount = 0;
|
||||
}
|
||||
}
|
||||
72
src/main/java/com/yaohun/mcbot/util/CDTimeAPI.java
Normal file
72
src/main/java/com/yaohun/mcbot/util/CDTimeAPI.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package com.yaohun.mcbot.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CDTimeAPI {
|
||||
|
||||
private static HashSet<CDData> cdData = new HashSet<>();
|
||||
|
||||
public static CDData getCDData(long tencentQQ) {
|
||||
for (CDData cDData : cdData) {
|
||||
if (cDData.getTencentQQ() == tencentQQ) {
|
||||
return cDData;
|
||||
}
|
||||
}
|
||||
CDData data = new CDData(tencentQQ);
|
||||
cdData.add(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void setPlayerCD(long tencentQQ, String key, long mills) {
|
||||
CDData data = getCDData(tencentQQ);
|
||||
long now = System.currentTimeMillis();
|
||||
data.setCD(key, now + mills);
|
||||
}
|
||||
|
||||
public static void addPlayerCD(long tencentQQ, String key, long mills) {
|
||||
if(isCD(tencentQQ,key)){
|
||||
setPlayerCD(tencentQQ,key,getCD(tencentQQ,key) + mills);
|
||||
} else {
|
||||
setPlayerCD(tencentQQ,key,mills);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getCD(long tencentQQ, String key) {
|
||||
CDData data = getCDData(tencentQQ);
|
||||
long now = System.currentTimeMillis();
|
||||
return data.getCD(key) - now;
|
||||
}
|
||||
|
||||
public static boolean isCD(long tencentQQ, String key) {
|
||||
return (getCD(tencentQQ, key) >= 0L);
|
||||
}
|
||||
|
||||
public static class CDData{
|
||||
private final long tencentQQ;
|
||||
|
||||
private final HashMap<String, Long> cdTime;
|
||||
|
||||
public CDData(long tencentQQ) {
|
||||
this.tencentQQ = tencentQQ;
|
||||
this.cdTime = new HashMap<>();
|
||||
}
|
||||
|
||||
public long getTencentQQ() {
|
||||
return tencentQQ;
|
||||
}
|
||||
|
||||
public void setCD(String key, long time) {
|
||||
this.cdTime.put(key, time);
|
||||
}
|
||||
|
||||
public long getCD(String key) {
|
||||
return this.cdTime.getOrDefault(key,-1L);
|
||||
}
|
||||
|
||||
public HashMap<String, Long> getCdTime() {
|
||||
return cdTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
376
src/main/java/org/java_websocket/AbstractWebSocket.java
Normal file
376
src/main/java/org/java_websocket/AbstractWebSocket.java
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.util.NamedThreadFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for additional implementations for the server as well as the client
|
||||
*/
|
||||
public abstract class AbstractWebSocket extends WebSocketAdapter {
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
private final Logger log = LoggerFactory.getLogger(AbstractWebSocket.class);
|
||||
|
||||
/**
|
||||
* Attribute which allows you to deactivate the Nagle's algorithm
|
||||
*
|
||||
* @since 1.3.3
|
||||
*/
|
||||
private boolean tcpNoDelay;
|
||||
|
||||
/**
|
||||
* Attribute which allows you to enable/disable the SO_REUSEADDR socket option.
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
private boolean reuseAddr;
|
||||
|
||||
/**
|
||||
* Attribute for a service that triggers lost connection checking
|
||||
*
|
||||
* @since 1.4.1
|
||||
*/
|
||||
private ScheduledExecutorService connectionLostCheckerService;
|
||||
/**
|
||||
* Attribute for a task that checks for lost connections
|
||||
*
|
||||
* @since 1.4.1
|
||||
*/
|
||||
private ScheduledFuture<?> connectionLostCheckerFuture;
|
||||
|
||||
/**
|
||||
* Attribute for the lost connection check interval in nanoseconds
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
private long connectionLostTimeout = TimeUnit.SECONDS.toNanos(60);
|
||||
|
||||
/**
|
||||
* Attribute to keep track if the WebSocket Server/Client is running/connected
|
||||
*
|
||||
* @since 1.3.9
|
||||
*/
|
||||
private boolean websocketRunning = false;
|
||||
|
||||
/**
|
||||
* Attribute to start internal threads as daemon
|
||||
*
|
||||
* @since 1.5.6
|
||||
*/
|
||||
private boolean daemon = false;
|
||||
|
||||
/**
|
||||
* Attribute to sync on
|
||||
*/
|
||||
private final Object syncConnectionLost = new Object();
|
||||
|
||||
/**
|
||||
* TCP receive buffer size that will be used for sockets (zero means use system default)
|
||||
*
|
||||
* @since 1.5.7
|
||||
*/
|
||||
private int receiveBufferSize = 0;
|
||||
|
||||
/**
|
||||
* Used for internal buffer allocations when the socket buffer size is not specified.
|
||||
*/
|
||||
protected static int DEFAULT_READ_BUFFER_SIZE = 65536;
|
||||
|
||||
/**
|
||||
* Get the interval checking for lost connections Default is 60 seconds
|
||||
*
|
||||
* @return the interval in seconds
|
||||
* @since 1.3.4
|
||||
*/
|
||||
public int getConnectionLostTimeout() {
|
||||
synchronized (syncConnectionLost) {
|
||||
return (int) TimeUnit.NANOSECONDS.toSeconds(connectionLostTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the interval checking for lost connections A value lower or equal 0 results in the
|
||||
* check to be deactivated
|
||||
*
|
||||
* @param connectionLostTimeout the interval in seconds
|
||||
* @since 1.3.4
|
||||
*/
|
||||
public void setConnectionLostTimeout(int connectionLostTimeout) {
|
||||
synchronized (syncConnectionLost) {
|
||||
this.connectionLostTimeout = TimeUnit.SECONDS.toNanos(connectionLostTimeout);
|
||||
if (this.connectionLostTimeout <= 0) {
|
||||
log.trace("Connection lost timer stopped");
|
||||
cancelConnectionLostTimer();
|
||||
return;
|
||||
}
|
||||
if (this.websocketRunning) {
|
||||
log.trace("Connection lost timer restarted");
|
||||
//Reset all the pings
|
||||
try {
|
||||
ArrayList<WebSocket> connections = new ArrayList<>(getConnections());
|
||||
WebSocketImpl webSocketImpl;
|
||||
for (WebSocket conn : connections) {
|
||||
if (conn instanceof WebSocketImpl) {
|
||||
webSocketImpl = (WebSocketImpl) conn;
|
||||
webSocketImpl.updateLastPong();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Exception during connection lost restart", e);
|
||||
}
|
||||
restartConnectionLostTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the connection lost timer
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
protected void stopConnectionLostTimer() {
|
||||
synchronized (syncConnectionLost) {
|
||||
if (connectionLostCheckerService != null || connectionLostCheckerFuture != null) {
|
||||
this.websocketRunning = false;
|
||||
log.trace("Connection lost timer stopped");
|
||||
cancelConnectionLostTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the connection lost timer
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
protected void startConnectionLostTimer() {
|
||||
synchronized (syncConnectionLost) {
|
||||
if (this.connectionLostTimeout <= 0) {
|
||||
log.trace("Connection lost timer deactivated");
|
||||
return;
|
||||
}
|
||||
log.trace("Connection lost timer started");
|
||||
this.websocketRunning = true;
|
||||
restartConnectionLostTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods allows the reset of the connection lost timer in case of a changed parameter
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
private void restartConnectionLostTimer() {
|
||||
cancelConnectionLostTimer();
|
||||
connectionLostCheckerService = Executors
|
||||
.newSingleThreadScheduledExecutor(new NamedThreadFactory("WebSocketConnectionLostChecker", daemon));
|
||||
Runnable connectionLostChecker = new Runnable() {
|
||||
|
||||
/**
|
||||
* Keep the connections in a separate list to not cause deadlocks
|
||||
*/
|
||||
private ArrayList<WebSocket> connections = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
connections.clear();
|
||||
try {
|
||||
connections.addAll(getConnections());
|
||||
long minimumPongTime;
|
||||
synchronized (syncConnectionLost) {
|
||||
minimumPongTime = (long) (System.nanoTime() - (connectionLostTimeout * 1.5));
|
||||
}
|
||||
for (WebSocket conn : connections) {
|
||||
executeConnectionLostDetection(conn, minimumPongTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//Ignore this exception
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
};
|
||||
|
||||
connectionLostCheckerFuture = connectionLostCheckerService
|
||||
.scheduleAtFixedRate(connectionLostChecker, connectionLostTimeout, connectionLostTimeout,
|
||||
TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a ping to the endpoint or close the connection since the other endpoint did not respond
|
||||
* with a ping
|
||||
*
|
||||
* @param webSocket the websocket instance
|
||||
* @param minimumPongTime the lowest/oldest allowable last pong time (in nanoTime) before we
|
||||
* consider the connection to be lost
|
||||
*/
|
||||
private void executeConnectionLostDetection(WebSocket webSocket, long minimumPongTime) {
|
||||
if (!(webSocket instanceof WebSocketImpl)) {
|
||||
return;
|
||||
}
|
||||
WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket;
|
||||
if (webSocketImpl.getLastPong() < minimumPongTime) {
|
||||
log.trace("Closing connection due to no pong received: {}", webSocketImpl);
|
||||
webSocketImpl.closeConnection(CloseFrame.ABNORMAL_CLOSE,
|
||||
"The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection");
|
||||
} else {
|
||||
if (webSocketImpl.isOpen()) {
|
||||
webSocketImpl.sendPing();
|
||||
} else {
|
||||
log.trace("Trying to ping a non open connection: {}", webSocketImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter to get all the currently available connections
|
||||
*
|
||||
* @return the currently available connections
|
||||
* @since 1.3.4
|
||||
*/
|
||||
protected abstract Collection<WebSocket> getConnections();
|
||||
|
||||
/**
|
||||
* Cancel any running timer for the connection lost detection
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
private void cancelConnectionLostTimer() {
|
||||
if (connectionLostCheckerService != null) {
|
||||
connectionLostCheckerService.shutdownNow();
|
||||
connectionLostCheckerService = null;
|
||||
}
|
||||
if (connectionLostCheckerFuture != null) {
|
||||
connectionLostCheckerFuture.cancel(false);
|
||||
connectionLostCheckerFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if TCP_NODELAY is enabled.
|
||||
*
|
||||
* @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections.
|
||||
* @since 1.3.3
|
||||
*/
|
||||
public boolean isTcpNoDelay() {
|
||||
return tcpNoDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for tcpNoDelay
|
||||
* <p>
|
||||
* Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections
|
||||
*
|
||||
* @param tcpNoDelay true to enable TCP_NODELAY, false to disable.
|
||||
* @since 1.3.3
|
||||
*/
|
||||
public void setTcpNoDelay(boolean tcpNoDelay) {
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Tests if SO_REUSEADDR is enabled.
|
||||
*
|
||||
* @return a boolean indicating whether or not SO_REUSEADDR is enabled.
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public boolean isReuseAddr() {
|
||||
return reuseAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for soReuseAddr
|
||||
* <p>
|
||||
* Enable/disable SO_REUSEADDR for the socket
|
||||
*
|
||||
* @param reuseAddr whether to enable or disable SO_REUSEADDR
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public void setReuseAddr(boolean reuseAddr) {
|
||||
this.reuseAddr = reuseAddr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for daemon
|
||||
*
|
||||
* @return whether internal threads are spawned in daemon mode
|
||||
* @since 1.5.6
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for daemon
|
||||
* <p>
|
||||
* Controls whether or not internal threads are spawned in daemon mode
|
||||
*
|
||||
* @since 1.5.6
|
||||
*/
|
||||
public void setDaemon(boolean daemon) {
|
||||
this.daemon = daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TCP receive buffer size that will be used for sockets (or zero, if not explicitly set).
|
||||
* @see java.net.Socket#setReceiveBufferSize(int)
|
||||
*
|
||||
* @since 1.5.7
|
||||
*/
|
||||
public int getReceiveBufferSize() {
|
||||
return receiveBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TCP receive buffer size that will be used for sockets.
|
||||
* If this is not explicitly set (or set to zero), the system default is used.
|
||||
* @see java.net.Socket#setReceiveBufferSize(int)
|
||||
*
|
||||
* @since 1.5.7
|
||||
*/
|
||||
public void setReceiveBufferSize(int receiveBufferSize) {
|
||||
if (receiveBufferSize < 0) {
|
||||
throw new IllegalArgumentException("buffer size < 0");
|
||||
}
|
||||
this.receiveBufferSize = receiveBufferSize;
|
||||
}
|
||||
|
||||
}
|
||||
111
src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
Normal file
111
src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class AbstractWrappedByteChannel implements WrappedByteChannel {
|
||||
|
||||
private final ByteChannel channel;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public AbstractWrappedByteChannel(ByteChannel towrap) {
|
||||
this.channel = towrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public AbstractWrappedByteChannel(WrappedByteChannel towrap) {
|
||||
this.channel = towrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
return channel.read(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return channel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
return channel.write(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedWrite() {
|
||||
return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMore() throws IOException {
|
||||
if (channel instanceof WrappedByteChannel) {
|
||||
((WrappedByteChannel) channel).writeMore();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedRead() {
|
||||
return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedRead();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMore(ByteBuffer dst) throws IOException {
|
||||
return channel instanceof WrappedByteChannel ? ((WrappedByteChannel) channel).readMore(dst) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlocking() {
|
||||
if (channel instanceof SocketChannel) {
|
||||
return ((SocketChannel) channel).isBlocking();
|
||||
} else if (channel instanceof WrappedByteChannel) {
|
||||
return ((WrappedByteChannel) channel).isBlocking();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
539
src/main/java/org/java_websocket/SSLSocketChannel.java
Normal file
539
src/main/java/org/java_websocket/SSLSocketChannel.java
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.java_websocket.interfaces.ISSLChannel;
|
||||
import org.java_websocket.util.ByteBufferUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* A class that represents an SSL/TLS peer, and can be extended to create a client or a server.
|
||||
* <p>
|
||||
* It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which is
|
||||
* described by Oracle as "an advanced API, not appropriate for casual use", since it requires the
|
||||
* user to implement much of the communication establishment procedure himself. More information
|
||||
* about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine
|
||||
* <p>
|
||||
* {@link SSLSocketChannel} implements the handshake protocol, required to establish a connection
|
||||
* between two peers, which is common for both client and server and provides the abstract {@link
|
||||
* SSLSocketChannel#read(ByteBuffer)} and {@link SSLSocketChannel#write(ByteBuffer)} (String)}
|
||||
* methods, that need to be implemented by the specific SSL/TLS peer that is going to extend this
|
||||
* class.
|
||||
*
|
||||
* @author <a href="mailto:alex.a.karnezis@gmail.com">Alex Karnezis</a>
|
||||
* <p>
|
||||
* Modified by marci4 to allow the usage as a ByteChannel
|
||||
* <p>
|
||||
* Permission for usage received at May 25, 2017 by Alex Karnezis
|
||||
*/
|
||||
public class SSLSocketChannel implements WrappedByteChannel, ByteChannel, ISSLChannel {
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
private final Logger log = LoggerFactory.getLogger(SSLSocketChannel.class);
|
||||
|
||||
/**
|
||||
* The underlying socket channel
|
||||
*/
|
||||
private final SocketChannel socketChannel;
|
||||
|
||||
/**
|
||||
* The engine which will be used for un-/wrapping of buffers
|
||||
*/
|
||||
private final SSLEngine engine;
|
||||
|
||||
|
||||
/**
|
||||
* Will contain this peer's application data in plaintext, that will be later encrypted using
|
||||
* {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can
|
||||
* typically be of any size, as long as it is large enough to contain this peer's outgoing
|
||||
* messages. If this peer tries to send a message bigger than buffer's capacity a {@link
|
||||
* BufferOverflowException} will be thrown.
|
||||
*/
|
||||
private ByteBuffer myAppData;
|
||||
|
||||
/**
|
||||
* Will contain this peer's encrypted data, that will be generated after {@link
|
||||
* SSLEngine#wrap(ByteBuffer, ByteBuffer)} is applied on {@link SSLSocketChannel#myAppData}. It
|
||||
* should be initialized using {@link SSLSession#getPacketBufferSize()}, which returns the size up
|
||||
* to which, SSL/TLS packets will be generated from the engine under a session. All SSLEngine
|
||||
* network buffers should be sized at least this large to avoid insufficient space problems when
|
||||
* performing wrap and unwrap calls.
|
||||
*/
|
||||
private ByteBuffer myNetData;
|
||||
|
||||
/**
|
||||
* Will contain the other peer's (decrypted) application data. It must be large enough to hold the
|
||||
* application data from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()}
|
||||
* for an estimation of the other peer's application data and should be enlarged if this size is
|
||||
* not enough.
|
||||
*/
|
||||
private ByteBuffer peerAppData;
|
||||
|
||||
/**
|
||||
* Will contain the other peer's encrypted data. The SSL/TLS protocols specify that
|
||||
* implementations should produce packets containing at most 16 KB of plaintext, so a buffer sized
|
||||
* to this value should normally cause no capacity problems. However, some implementations violate
|
||||
* the specification and generate large records up to 32 KB. If the {@link
|
||||
* SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes
|
||||
* returned by SSLSession will be updated dynamically, so the this peer should check for overflow
|
||||
* conditions and enlarge the buffer using the session's (updated) buffer size.
|
||||
*/
|
||||
private ByteBuffer peerNetData;
|
||||
|
||||
/**
|
||||
* Will be used to execute tasks that may emerge during handshake in parallel with the server's
|
||||
* main thread.
|
||||
*/
|
||||
private ExecutorService executor;
|
||||
|
||||
|
||||
public SSLSocketChannel(SocketChannel inputSocketChannel, SSLEngine inputEngine,
|
||||
ExecutorService inputExecutor, SelectionKey key) throws IOException {
|
||||
if (inputSocketChannel == null || inputEngine == null || executor == inputExecutor) {
|
||||
throw new IllegalArgumentException("parameter must not be null");
|
||||
}
|
||||
|
||||
this.socketChannel = inputSocketChannel;
|
||||
this.engine = inputEngine;
|
||||
this.executor = inputExecutor;
|
||||
myNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
|
||||
peerNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
|
||||
this.engine.beginHandshake();
|
||||
if (doHandshake()) {
|
||||
if (key != null) {
|
||||
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
socketChannel.close();
|
||||
} catch (IOException e) {
|
||||
log.error("Exception during the closing of the channel", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(ByteBuffer dst) throws IOException {
|
||||
if (!dst.hasRemaining()) {
|
||||
return 0;
|
||||
}
|
||||
if (peerAppData.hasRemaining()) {
|
||||
peerAppData.flip();
|
||||
return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
|
||||
}
|
||||
peerNetData.compact();
|
||||
|
||||
int bytesRead = socketChannel.read(peerNetData);
|
||||
/*
|
||||
* If bytesRead are 0 put we still have some data in peerNetData still to an unwrap (for testcase 1.1.6)
|
||||
*/
|
||||
if (bytesRead > 0 || peerNetData.hasRemaining()) {
|
||||
peerNetData.flip();
|
||||
while (peerNetData.hasRemaining()) {
|
||||
peerAppData.compact();
|
||||
SSLEngineResult result;
|
||||
try {
|
||||
result = engine.unwrap(peerNetData, peerAppData);
|
||||
} catch (SSLException e) {
|
||||
log.error("SSLException during unwrap", e);
|
||||
throw e;
|
||||
}
|
||||
switch (result.getStatus()) {
|
||||
case OK:
|
||||
peerAppData.flip();
|
||||
return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
|
||||
case BUFFER_UNDERFLOW:
|
||||
peerAppData.flip();
|
||||
return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
|
||||
case BUFFER_OVERFLOW:
|
||||
peerAppData = enlargeApplicationBuffer(peerAppData);
|
||||
return read(dst);
|
||||
case CLOSED:
|
||||
closeConnection();
|
||||
dst.clear();
|
||||
return -1;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
|
||||
}
|
||||
}
|
||||
} else if (bytesRead < 0) {
|
||||
handleEndOfStream();
|
||||
}
|
||||
ByteBufferUtils.transferByteBuffer(peerAppData, dst);
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int write(ByteBuffer output) throws IOException {
|
||||
int num = 0;
|
||||
while (output.hasRemaining()) {
|
||||
// The loop has a meaning for (outgoing) messages larger than 16KB.
|
||||
// Every wrap call will remove 16KB from the original message and send it to the remote peer.
|
||||
myNetData.clear();
|
||||
SSLEngineResult result = engine.wrap(output, myNetData);
|
||||
switch (result.getStatus()) {
|
||||
case OK:
|
||||
myNetData.flip();
|
||||
while (myNetData.hasRemaining()) {
|
||||
num += socketChannel.write(myNetData);
|
||||
}
|
||||
break;
|
||||
case BUFFER_OVERFLOW:
|
||||
myNetData = enlargePacketBuffer(myNetData);
|
||||
break;
|
||||
case BUFFER_UNDERFLOW:
|
||||
throw new SSLException(
|
||||
"Buffer underflow occurred after a wrap. I don't think we should ever get here.");
|
||||
case CLOSED:
|
||||
closeConnection();
|
||||
return 0;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the handshake protocol between two peers, required for the establishment of the
|
||||
* SSL/TLS connection. During the handshake, encryption configuration information - such as the
|
||||
* list of available cipher suites - will be exchanged and if the handshake is successful will
|
||||
* lead to an established SSL/TLS session.
|
||||
* <p>
|
||||
* <p/>
|
||||
* A typical handshake will usually contain the following steps:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>1. wrap: ClientHello</li>
|
||||
* <li>2. unwrap: ServerHello/Cert/ServerHelloDone</li>
|
||||
* <li>3. wrap: ClientKeyExchange</li>
|
||||
* <li>4. wrap: ChangeCipherSpec</li>
|
||||
* <li>5. wrap: Finished</li>
|
||||
* <li>6. unwrap: ChangeCipherSpec</li>
|
||||
* <li>7. unwrap: Finished</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Handshake is also used during the end of the session, in order to properly close the connection between the two peers.
|
||||
* A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for
|
||||
* the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message
|
||||
* from his peer and then enter the handshake procedure to send his own CLOSE message as well.
|
||||
*
|
||||
* @return True if the connection handshake was successful or false if an error occurred.
|
||||
* @throws IOException - if an error occurs during read/write to the socket channel.
|
||||
*/
|
||||
private boolean doHandshake() throws IOException {
|
||||
SSLEngineResult result;
|
||||
HandshakeStatus handshakeStatus;
|
||||
|
||||
// NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer
|
||||
// will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less
|
||||
// than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers
|
||||
// to be used for the handshake, while keeping client's buffers at the same size.
|
||||
int appBufferSize = engine.getSession().getApplicationBufferSize();
|
||||
myAppData = ByteBuffer.allocate(appBufferSize);
|
||||
peerAppData = ByteBuffer.allocate(appBufferSize);
|
||||
myNetData.clear();
|
||||
peerNetData.clear();
|
||||
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
boolean handshakeComplete = false;
|
||||
while (!handshakeComplete) {
|
||||
switch (handshakeStatus) {
|
||||
case FINISHED:
|
||||
handshakeComplete = !this.peerNetData.hasRemaining();
|
||||
if (handshakeComplete) {
|
||||
return true;
|
||||
}
|
||||
socketChannel.write(this.peerNetData);
|
||||
break;
|
||||
case NEED_UNWRAP:
|
||||
if (socketChannel.read(peerNetData) < 0) {
|
||||
if (engine.isInboundDone() && engine.isOutboundDone()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
engine.closeInbound();
|
||||
} catch (SSLException e) {
|
||||
//Ignore, can't do anything against this exception
|
||||
}
|
||||
engine.closeOutbound();
|
||||
// After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client.
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
break;
|
||||
}
|
||||
peerNetData.flip();
|
||||
try {
|
||||
result = engine.unwrap(peerNetData, peerAppData);
|
||||
peerNetData.compact();
|
||||
handshakeStatus = result.getHandshakeStatus();
|
||||
} catch (SSLException sslException) {
|
||||
engine.closeOutbound();
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
break;
|
||||
}
|
||||
switch (result.getStatus()) {
|
||||
case OK:
|
||||
break;
|
||||
case BUFFER_OVERFLOW:
|
||||
// Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
|
||||
peerAppData = enlargeApplicationBuffer(peerAppData);
|
||||
break;
|
||||
case BUFFER_UNDERFLOW:
|
||||
// Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data.
|
||||
peerNetData = handleBufferUnderflow(peerNetData);
|
||||
break;
|
||||
case CLOSED:
|
||||
if (engine.isOutboundDone()) {
|
||||
return false;
|
||||
} else {
|
||||
engine.closeOutbound();
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
|
||||
}
|
||||
break;
|
||||
case NEED_WRAP:
|
||||
myNetData.clear();
|
||||
try {
|
||||
result = engine.wrap(myAppData, myNetData);
|
||||
handshakeStatus = result.getHandshakeStatus();
|
||||
} catch (SSLException sslException) {
|
||||
engine.closeOutbound();
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
break;
|
||||
}
|
||||
switch (result.getStatus()) {
|
||||
case OK:
|
||||
myNetData.flip();
|
||||
while (myNetData.hasRemaining()) {
|
||||
socketChannel.write(myNetData);
|
||||
}
|
||||
break;
|
||||
case BUFFER_OVERFLOW:
|
||||
// Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap.
|
||||
// Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed
|
||||
// to produce messages smaller or equal to that, but a general handling would be the following:
|
||||
myNetData = enlargePacketBuffer(myNetData);
|
||||
break;
|
||||
case BUFFER_UNDERFLOW:
|
||||
throw new SSLException(
|
||||
"Buffer underflow occurred after a wrap. I don't think we should ever get here.");
|
||||
case CLOSED:
|
||||
try {
|
||||
myNetData.flip();
|
||||
while (myNetData.hasRemaining()) {
|
||||
socketChannel.write(myNetData);
|
||||
}
|
||||
// At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read.
|
||||
peerNetData.clear();
|
||||
} catch (Exception e) {
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
|
||||
}
|
||||
break;
|
||||
case NEED_TASK:
|
||||
Runnable task;
|
||||
while ((task = engine.getDelegatedTask()) != null) {
|
||||
executor.execute(task);
|
||||
}
|
||||
handshakeStatus = engine.getHandshakeStatus();
|
||||
break;
|
||||
|
||||
case NOT_HANDSHAKING:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarging a packet buffer (peerNetData or myNetData)
|
||||
*
|
||||
* @param buffer the buffer to enlarge
|
||||
* @return the enlarged buffer
|
||||
*/
|
||||
private ByteBuffer enlargePacketBuffer(ByteBuffer buffer) {
|
||||
return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarging a packet buffer (peerAppData or myAppData)
|
||||
*
|
||||
* @param buffer the buffer to enlarge
|
||||
* @return the enlarged buffer
|
||||
*/
|
||||
private ByteBuffer enlargeApplicationBuffer(ByteBuffer buffer) {
|
||||
return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares sessionProposedCapacity with buffer's capacity. If buffer's capacity is
|
||||
* smaller, returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer
|
||||
* with capacity twice the size of the initial one.
|
||||
*
|
||||
* @param buffer - the buffer to be enlarged.
|
||||
* @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link
|
||||
* SSLSession}.
|
||||
* @return A new buffer with a larger capacity.
|
||||
*/
|
||||
private ByteBuffer enlargeBuffer(ByteBuffer buffer, int sessionProposedCapacity) {
|
||||
if (sessionProposedCapacity > buffer.capacity()) {
|
||||
buffer = ByteBuffer.allocate(sessionProposedCapacity);
|
||||
} else {
|
||||
buffer = ByteBuffer.allocate(buffer.capacity() * 2);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already
|
||||
* filled, and if there is no space problem will return the same buffer, so the client tries to
|
||||
* read again. If the buffer is already filled will try to enlarge the buffer either to session's
|
||||
* proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so
|
||||
* the buffer will always be a peerNetData buffer.
|
||||
*
|
||||
* @param buffer - will always be peerNetData buffer.
|
||||
* @return The same buffer if there is no space problem or a new buffer with the same data but
|
||||
* more space.
|
||||
*/
|
||||
private ByteBuffer handleBufferUnderflow(ByteBuffer buffer) {
|
||||
if (engine.getSession().getPacketBufferSize() < buffer.limit()) {
|
||||
return buffer;
|
||||
} else {
|
||||
ByteBuffer replaceBuffer = enlargePacketBuffer(buffer);
|
||||
buffer.flip();
|
||||
replaceBuffer.put(buffer);
|
||||
return replaceBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called when this peer wants to explicitly close the connection or when a
|
||||
* close message has arrived from the other peer, in order to provide an orderly shutdown.
|
||||
* <p/>
|
||||
* It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close
|
||||
* message and sets {@link SSLEngine} to the <code>NEED_WRAP</code> state. Then, it delegates the
|
||||
* exchange of close messages to the handshake method and finally, it closes socket channel.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs to the socket channel.
|
||||
*/
|
||||
private void closeConnection() throws IOException {
|
||||
engine.closeOutbound();
|
||||
try {
|
||||
doHandshake();
|
||||
} catch (IOException e) {
|
||||
//Just ignore this exception since we are closing the connection already
|
||||
}
|
||||
socketChannel.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link
|
||||
* (socket channel) is severed before close messages are exchanged. This may happen by getting an
|
||||
* -1 or {@link IOException} when trying to read from the socket channel, or an {@link
|
||||
* IOException} when trying to write to it. In both cases {@link SSLEngine#closeInbound()} should
|
||||
* be called and then try to follow the standard procedure.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs to the socket channel.
|
||||
*/
|
||||
private void handleEndOfStream() throws IOException {
|
||||
try {
|
||||
engine.closeInbound();
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
|
||||
}
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedWrite() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMore() throws IOException {
|
||||
//Nothing to do since we write out all the data in a while loop
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedRead() {
|
||||
return peerNetData.hasRemaining() || peerAppData.hasRemaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMore(ByteBuffer dst) throws IOException {
|
||||
return read(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlocking() {
|
||||
return socketChannel.isBlocking();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return socketChannel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine getSSLEngine() {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
501
src/main/java/org/java_websocket/SSLSocketChannel2.java
Normal file
501
src/main/java/org/java_websocket/SSLSocketChannel2.java
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLEngineResult.Status;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.java_websocket.interfaces.ISSLChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper.
|
||||
*/
|
||||
public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel, ISSLChannel {
|
||||
|
||||
/**
|
||||
* This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the
|
||||
* handshake phase.
|
||||
**/
|
||||
protected static ByteBuffer emptybuffer = ByteBuffer.allocate(0);
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
private final Logger log = LoggerFactory.getLogger(SSLSocketChannel2.class);
|
||||
|
||||
protected ExecutorService exec;
|
||||
|
||||
protected List<Future<?>> tasks;
|
||||
|
||||
/**
|
||||
* raw payload incoming
|
||||
*/
|
||||
protected ByteBuffer inData;
|
||||
/**
|
||||
* encrypted data outgoing
|
||||
*/
|
||||
protected ByteBuffer outCrypt;
|
||||
/**
|
||||
* encrypted data incoming
|
||||
*/
|
||||
protected ByteBuffer inCrypt;
|
||||
|
||||
/**
|
||||
* the underlying channel
|
||||
*/
|
||||
protected SocketChannel socketChannel;
|
||||
/**
|
||||
* used to set interestOP SelectionKey.OP_WRITE for the underlying channel
|
||||
*/
|
||||
protected SelectionKey selectionKey;
|
||||
|
||||
protected SSLEngine sslEngine;
|
||||
protected SSLEngineResult readEngineResult;
|
||||
protected SSLEngineResult writeEngineResult;
|
||||
|
||||
/**
|
||||
* Should be used to count the buffer allocations. But because of #190 where
|
||||
* HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to
|
||||
* check whether {@link #createBuffers(SSLSession)} needs to be called.
|
||||
**/
|
||||
protected int bufferallocations = 0;
|
||||
|
||||
public SSLSocketChannel2(SocketChannel channel, SSLEngine sslEngine, ExecutorService exec,
|
||||
SelectionKey key) throws IOException {
|
||||
if (channel == null || sslEngine == null || exec == null) {
|
||||
throw new IllegalArgumentException("parameter must not be null");
|
||||
}
|
||||
|
||||
this.socketChannel = channel;
|
||||
this.sslEngine = sslEngine;
|
||||
this.exec = exec;
|
||||
|
||||
readEngineResult = writeEngineResult = new SSLEngineResult(Status.BUFFER_UNDERFLOW,
|
||||
sslEngine.getHandshakeStatus(), 0, 0); // init to prevent NPEs
|
||||
|
||||
tasks = new ArrayList<Future<?>>(3);
|
||||
if (key != null) {
|
||||
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
|
||||
this.selectionKey = key;
|
||||
}
|
||||
createBuffers(sslEngine.getSession());
|
||||
// kick off handshake
|
||||
socketChannel.write(wrap(emptybuffer));// initializes res
|
||||
processHandshake(false);
|
||||
}
|
||||
|
||||
private void consumeFutureUninterruptible(Future<?> f) {
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
f.get();
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will do whatever necessary to process the sslEngine handshake. Thats why it's
|
||||
* called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)}
|
||||
**/
|
||||
private synchronized void processHandshake(boolean isReading) throws IOException {
|
||||
if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
|
||||
return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking.
|
||||
}
|
||||
if (!tasks.isEmpty()) {
|
||||
Iterator<Future<?>> it = tasks.iterator();
|
||||
while (it.hasNext()) {
|
||||
Future<?> f = it.next();
|
||||
if (f.isDone()) {
|
||||
it.remove();
|
||||
} else {
|
||||
if (isBlocking()) {
|
||||
consumeFutureUninterruptible(f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isReading && sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
|
||||
if (!isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
|
||||
inCrypt.compact();
|
||||
int read = socketChannel.read(inCrypt);
|
||||
if (read == -1) {
|
||||
throw new IOException("connection closed unexpectedly by peer");
|
||||
}
|
||||
inCrypt.flip();
|
||||
}
|
||||
inData.compact();
|
||||
unwrap();
|
||||
if (readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
|
||||
createBuffers(sslEngine.getSession());
|
||||
return;
|
||||
}
|
||||
}
|
||||
consumeDelegatedTasks();
|
||||
if (tasks.isEmpty()
|
||||
|| sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
|
||||
socketChannel.write(wrap(emptybuffer));
|
||||
if (writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
|
||||
createBuffers(sslEngine.getSession());
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert (sslEngine.getHandshakeStatus()
|
||||
!= HandshakeStatus.NOT_HANDSHAKING);// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED
|
||||
|
||||
bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur.
|
||||
}
|
||||
|
||||
private synchronized ByteBuffer wrap(ByteBuffer b) throws SSLException {
|
||||
outCrypt.compact();
|
||||
writeEngineResult = sslEngine.wrap(b, outCrypt);
|
||||
outCrypt.flip();
|
||||
return outCrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData}
|
||||
**/
|
||||
private synchronized ByteBuffer unwrap() throws SSLException {
|
||||
int rem;
|
||||
//There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458)
|
||||
if (readEngineResult.getStatus() == Status.CLOSED
|
||||
&& sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
|
||||
try {
|
||||
close();
|
||||
} catch (IOException e) {
|
||||
//Not really interesting
|
||||
}
|
||||
}
|
||||
do {
|
||||
rem = inData.remaining();
|
||||
readEngineResult = sslEngine.unwrap(inCrypt, inData);
|
||||
} while (readEngineResult.getStatus() == Status.OK && (rem != inData.remaining()
|
||||
|| sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP));
|
||||
inData.flip();
|
||||
return inData;
|
||||
}
|
||||
|
||||
protected void consumeDelegatedTasks() {
|
||||
Runnable task;
|
||||
while ((task = sslEngine.getDelegatedTask()) != null) {
|
||||
tasks.add(exec.submit(task));
|
||||
// task.run();
|
||||
}
|
||||
}
|
||||
|
||||
protected void createBuffers(SSLSession session) {
|
||||
saveCryptedData(); // save any remaining data in inCrypt
|
||||
int netBufferMax = session.getPacketBufferSize();
|
||||
int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax);
|
||||
|
||||
if (inData == null) {
|
||||
inData = ByteBuffer.allocate(appBufferMax);
|
||||
outCrypt = ByteBuffer.allocate(netBufferMax);
|
||||
inCrypt = ByteBuffer.allocate(netBufferMax);
|
||||
} else {
|
||||
if (inData.capacity() != appBufferMax) {
|
||||
inData = ByteBuffer.allocate(appBufferMax);
|
||||
}
|
||||
if (outCrypt.capacity() != netBufferMax) {
|
||||
outCrypt = ByteBuffer.allocate(netBufferMax);
|
||||
}
|
||||
if (inCrypt.capacity() != netBufferMax) {
|
||||
inCrypt = ByteBuffer.allocate(netBufferMax);
|
||||
}
|
||||
}
|
||||
if (inData.remaining() != 0 && log.isTraceEnabled()) {
|
||||
log.trace(new String(inData.array(), inData.position(), inData.remaining()));
|
||||
}
|
||||
inData.rewind();
|
||||
inData.flip();
|
||||
if (inCrypt.remaining() != 0 && log.isTraceEnabled()) {
|
||||
log.trace(new String(inCrypt.array(), inCrypt.position(), inCrypt.remaining()));
|
||||
}
|
||||
inCrypt.rewind();
|
||||
inCrypt.flip();
|
||||
outCrypt.rewind();
|
||||
outCrypt.flip();
|
||||
bufferallocations++;
|
||||
}
|
||||
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
if (!isHandShakeComplete()) {
|
||||
processHandshake(false);
|
||||
return 0;
|
||||
}
|
||||
// assert(bufferallocations > 1); // see #190
|
||||
// if(bufferallocations <= 1) {
|
||||
// createBuffers(sslEngine.getSession());
|
||||
// }
|
||||
int num = socketChannel.write(wrap(src));
|
||||
if (writeEngineResult.getStatus() == Status.CLOSED) {
|
||||
throw new EOFException("Connection is closed");
|
||||
}
|
||||
return num;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks when in blocking mode until at least one byte has been decoded.<br> When not in blocking
|
||||
* mode 0 may be returned.
|
||||
*
|
||||
* @return the number of bytes read.
|
||||
**/
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
tryRestoreCryptedData();
|
||||
while (true) {
|
||||
if (!dst.hasRemaining()) {
|
||||
return 0;
|
||||
}
|
||||
if (!isHandShakeComplete()) {
|
||||
if (isBlocking()) {
|
||||
while (!isHandShakeComplete()) {
|
||||
processHandshake(true);
|
||||
}
|
||||
} else {
|
||||
processHandshake(true);
|
||||
if (!isHandShakeComplete()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// assert(bufferallocations > 1); // see #190
|
||||
// if (bufferallocations <= 1) {
|
||||
// createBuffers(sslEngine.getSession());
|
||||
// }
|
||||
|
||||
/* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call.
|
||||
* 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining)
|
||||
*/
|
||||
int purged = readRemaining(dst);
|
||||
if (purged != 0) {
|
||||
return purged;
|
||||
}
|
||||
|
||||
/* We only continue when we really need more data from the network.
|
||||
* Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption
|
||||
*/
|
||||
assert (inData.position() == 0);
|
||||
inData.clear();
|
||||
|
||||
if (!inCrypt.hasRemaining()) {
|
||||
inCrypt.clear();
|
||||
} else {
|
||||
inCrypt.compact();
|
||||
}
|
||||
|
||||
if (isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
|
||||
if (socketChannel.read(inCrypt) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
inCrypt.flip();
|
||||
unwrap();
|
||||
|
||||
int transferred = transfereTo(inData, dst);
|
||||
if (transferred == 0 && isBlocking()) {
|
||||
continue;
|
||||
}
|
||||
return transferred;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt)
|
||||
**/
|
||||
private int readRemaining(ByteBuffer dst) throws SSLException {
|
||||
if (inData.hasRemaining()) {
|
||||
return transfereTo(inData, dst);
|
||||
}
|
||||
if (!inData.hasRemaining()) {
|
||||
inData.clear();
|
||||
}
|
||||
tryRestoreCryptedData();
|
||||
// test if some bytes left from last read (e.g. BUFFER_UNDERFLOW)
|
||||
if (inCrypt.hasRemaining()) {
|
||||
unwrap();
|
||||
int amount = transfereTo(inData, dst);
|
||||
if (readEngineResult.getStatus() == Status.CLOSED) {
|
||||
return -1;
|
||||
}
|
||||
if (amount > 0) {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return socketChannel.isConnected();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
sslEngine.closeOutbound();
|
||||
sslEngine.getSession().invalidate();
|
||||
try {
|
||||
if (socketChannel.isOpen()) {
|
||||
socketChannel.write(wrap(emptybuffer));
|
||||
}
|
||||
} finally { // in case socketChannel.write produce exception - channel will never close
|
||||
socketChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHandShakeComplete() {
|
||||
HandshakeStatus status = sslEngine.getHandshakeStatus();
|
||||
return status == HandshakeStatus.FINISHED
|
||||
|| status == HandshakeStatus.NOT_HANDSHAKING;
|
||||
}
|
||||
|
||||
public SelectableChannel configureBlocking(boolean b) throws IOException {
|
||||
return socketChannel.configureBlocking(b);
|
||||
}
|
||||
|
||||
public boolean connect(SocketAddress remote) throws IOException {
|
||||
return socketChannel.connect(remote);
|
||||
}
|
||||
|
||||
public boolean finishConnect() throws IOException {
|
||||
return socketChannel.finishConnect();
|
||||
}
|
||||
|
||||
public Socket socket() {
|
||||
return socketChannel.socket();
|
||||
}
|
||||
|
||||
public boolean isInboundDone() {
|
||||
return sslEngine.isInboundDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return socketChannel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedWrite() {
|
||||
return outCrypt.hasRemaining()
|
||||
|| !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMore() throws IOException {
|
||||
write(outCrypt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedRead() {
|
||||
return saveCryptData != null || inData.hasRemaining() || (inCrypt.hasRemaining()
|
||||
&& readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW
|
||||
&& readEngineResult.getStatus() != Status.CLOSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMore(ByteBuffer dst) throws SSLException {
|
||||
return readRemaining(dst);
|
||||
}
|
||||
|
||||
private int transfereTo(ByteBuffer from, ByteBuffer to) {
|
||||
int fremain = from.remaining();
|
||||
int toremain = to.remaining();
|
||||
if (fremain > toremain) {
|
||||
// FIXME there should be a more efficient transfer method
|
||||
int limit = Math.min(fremain, toremain);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
to.put(from.get());
|
||||
}
|
||||
return limit;
|
||||
} else {
|
||||
to.put(from);
|
||||
return fremain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlocking() {
|
||||
return socketChannel.isBlocking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine getSSLEngine() {
|
||||
return sslEngine;
|
||||
}
|
||||
|
||||
|
||||
// to avoid complexities with inCrypt, extra unwrapped data after SSL handshake will be saved off in a byte array
|
||||
// and the inserted back on first read
|
||||
private byte[] saveCryptData = null;
|
||||
|
||||
private void saveCryptedData() {
|
||||
// did we find any extra data?
|
||||
if (inCrypt != null && inCrypt.remaining() > 0) {
|
||||
int saveCryptSize = inCrypt.remaining();
|
||||
saveCryptData = new byte[saveCryptSize];
|
||||
inCrypt.get(saveCryptData);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryRestoreCryptedData() {
|
||||
// was there any extra data, then put into inCrypt and clean up
|
||||
if (saveCryptData != null) {
|
||||
inCrypt.clear();
|
||||
inCrypt.put(saveCryptData);
|
||||
inCrypt.flip();
|
||||
saveCryptData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/main/java/org/java_websocket/SocketChannelIOHelper.java
Normal file
116
src/main/java/org/java_websocket/SocketChannelIOHelper.java
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import org.java_websocket.enums.Role;
|
||||
|
||||
public class SocketChannelIOHelper {
|
||||
|
||||
private SocketChannelIOHelper() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static boolean read(final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel)
|
||||
throws IOException {
|
||||
buf.clear();
|
||||
int read = channel.read(buf);
|
||||
buf.flip();
|
||||
|
||||
if (read == -1) {
|
||||
ws.eot();
|
||||
return false;
|
||||
}
|
||||
return read != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param buf The ByteBuffer to read from
|
||||
* @param ws The WebSocketImpl associated with the channels
|
||||
* @param channel The channel to read from
|
||||
* @return returns Whether there is more data left which can be obtained via {@link
|
||||
* WrappedByteChannel#readMore(ByteBuffer)}
|
||||
* @throws IOException May be thrown by {@link WrappedByteChannel#readMore(ByteBuffer)}#
|
||||
* @see WrappedByteChannel#readMore(ByteBuffer)
|
||||
**/
|
||||
public static boolean readMore(final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel)
|
||||
throws IOException {
|
||||
buf.clear();
|
||||
int read = channel.readMore(buf);
|
||||
buf.flip();
|
||||
|
||||
if (read == -1) {
|
||||
ws.eot();
|
||||
return false;
|
||||
}
|
||||
return channel.isNeedRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the whole outQueue has been flushed
|
||||
*
|
||||
* @param ws The WebSocketImpl associated with the channels
|
||||
* @param sockchannel The channel to write to
|
||||
* @return returns Whether there is more data to write
|
||||
* @throws IOException May be thrown by {@link WrappedByteChannel#writeMore()}
|
||||
*/
|
||||
public static boolean batch(WebSocketImpl ws, ByteChannel sockchannel) throws IOException {
|
||||
if (ws == null) {
|
||||
return false;
|
||||
}
|
||||
ByteBuffer buffer = ws.outQueue.peek();
|
||||
WrappedByteChannel c = null;
|
||||
|
||||
if (buffer == null) {
|
||||
if (sockchannel instanceof WrappedByteChannel) {
|
||||
c = (WrappedByteChannel) sockchannel;
|
||||
if (c.isNeedWrite()) {
|
||||
c.writeMore();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
// FIXME writing as much as possible is unfair!!
|
||||
/*int written = */
|
||||
sockchannel.write(buffer);
|
||||
if (buffer.remaining() > 0) {
|
||||
return false;
|
||||
} else {
|
||||
ws.outQueue.poll(); // Buffer finished. Remove it.
|
||||
buffer = ws.outQueue.peek();
|
||||
}
|
||||
} while (buffer != null);
|
||||
}
|
||||
|
||||
if (ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft() != null
|
||||
&& ws.getDraft().getRole() != null && ws.getDraft().getRole() == Role.SERVER) {
|
||||
ws.closeConnection();
|
||||
}
|
||||
return c == null || !((WrappedByteChannel) sockchannel).isNeedWrite();
|
||||
}
|
||||
}
|
||||
251
src/main/java/org/java_websocket/WebSocket.java
Normal file
251
src/main/java/org/java_websocket/WebSocket.java
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.enums.ReadyState;
|
||||
import org.java_websocket.exceptions.WebsocketNotConnectedException;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.protocols.IProtocol;
|
||||
|
||||
public interface WebSocket {
|
||||
|
||||
/**
|
||||
* sends the closing handshake. may be send in response to an other handshake.
|
||||
*
|
||||
* @param code the closing code
|
||||
* @param message the closing message
|
||||
*/
|
||||
void close(int code, String message);
|
||||
|
||||
/**
|
||||
* sends the closing handshake. may be send in response to an other handshake.
|
||||
*
|
||||
* @param code the closing code
|
||||
*/
|
||||
void close(int code);
|
||||
|
||||
/**
|
||||
* Convenience function which behaves like close(CloseFrame.NORMAL)
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* This will close the connection immediately without a proper close handshake. The code and the
|
||||
* message therefore won't be transferred over the wire also they will be forwarded to
|
||||
* onClose/onWebsocketClose.
|
||||
*
|
||||
* @param code the closing code
|
||||
* @param message the closing message
|
||||
**/
|
||||
void closeConnection(int code, String message);
|
||||
|
||||
/**
|
||||
* Send Text data to the other end.
|
||||
*
|
||||
* @param text the text data to send
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
void send(String text);
|
||||
|
||||
/**
|
||||
* Send Binary data (plain bytes) to the other end.
|
||||
*
|
||||
* @param bytes the binary data to send
|
||||
* @throws IllegalArgumentException the data is null
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
void send(ByteBuffer bytes);
|
||||
|
||||
/**
|
||||
* Send Binary data (plain bytes) to the other end.
|
||||
*
|
||||
* @param bytes the byte array to send
|
||||
* @throws IllegalArgumentException the data is null
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
void send(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Send a frame to the other end
|
||||
*
|
||||
* @param framedata the frame to send to the other end
|
||||
*/
|
||||
void sendFrame(Framedata framedata);
|
||||
|
||||
/**
|
||||
* Send a collection of frames to the other end
|
||||
*
|
||||
* @param frames the frames to send to the other end
|
||||
*/
|
||||
void sendFrame(Collection<Framedata> frames);
|
||||
|
||||
/**
|
||||
* Send a ping to the other end
|
||||
*
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
void sendPing();
|
||||
|
||||
/**
|
||||
* Allows to send continuous/fragmented frames conveniently. <br> For more into on this frame type
|
||||
* see http://tools.ietf.org/html/rfc6455#section-5.4<br>
|
||||
* <p>
|
||||
* If the first frame you send is also the last then it is not a fragmented frame and will
|
||||
* received via onMessage instead of onFragmented even though it was send by this method.
|
||||
*
|
||||
* @param op This is only important for the first frame in the sequence. Opcode.TEXT,
|
||||
* Opcode.BINARY are allowed.
|
||||
* @param buffer The buffer which contains the payload. It may have no bytes remaining.
|
||||
* @param fin true means the current frame is the last in the sequence.
|
||||
**/
|
||||
void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin);
|
||||
|
||||
/**
|
||||
* Checks if the websocket has buffered data
|
||||
*
|
||||
* @return has the websocket buffered data
|
||||
*/
|
||||
boolean hasBufferedData();
|
||||
|
||||
/**
|
||||
* Returns the address of the endpoint this socket is connected to, or {@code null} if it is
|
||||
* unconnected.
|
||||
*
|
||||
* @return the remote socket address or null, if this socket is unconnected
|
||||
*/
|
||||
InetSocketAddress getRemoteSocketAddress();
|
||||
|
||||
/**
|
||||
* Returns the address of the endpoint this socket is bound to, or {@code null} if it is not
|
||||
* bound.
|
||||
*
|
||||
* @return the local socket address or null, if this socket is not bound
|
||||
*/
|
||||
InetSocketAddress getLocalSocketAddress();
|
||||
|
||||
/**
|
||||
* Is the websocket in the state OPEN
|
||||
*
|
||||
* @return state equals ReadyState.OPEN
|
||||
*/
|
||||
boolean isOpen();
|
||||
|
||||
/**
|
||||
* Is the websocket in the state CLOSING
|
||||
*
|
||||
* @return state equals ReadyState.CLOSING
|
||||
*/
|
||||
boolean isClosing();
|
||||
|
||||
/**
|
||||
* Returns true when no further frames may be submitted<br> This happens before the socket
|
||||
* connection is closed.
|
||||
*
|
||||
* @return true when no further frames may be submitted
|
||||
*/
|
||||
boolean isFlushAndClose();
|
||||
|
||||
/**
|
||||
* Is the websocket in the state CLOSED
|
||||
*
|
||||
* @return state equals ReadyState.CLOSED
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Getter for the draft
|
||||
*
|
||||
* @return the used draft
|
||||
*/
|
||||
Draft getDraft();
|
||||
|
||||
/**
|
||||
* Retrieve the WebSocket 'ReadyState'. This represents the state of the connection. It returns a
|
||||
* numerical value, as per W3C WebSockets specs.
|
||||
*
|
||||
* @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED'
|
||||
*/
|
||||
ReadyState getReadyState();
|
||||
|
||||
/**
|
||||
* Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2<br>
|
||||
* If the opening handshake has not yet happened it will return null.
|
||||
*
|
||||
* @return Returns the decoded path component of this URI.
|
||||
**/
|
||||
String getResourceDescriptor();
|
||||
|
||||
/**
|
||||
* Setter for an attachment on the socket connection. The attachment may be of any type.
|
||||
*
|
||||
* @param attachment The object to be attached to the user
|
||||
* @param <T> The type of the attachment
|
||||
* @since 1.3.7
|
||||
**/
|
||||
<T> void setAttachment(T attachment);
|
||||
|
||||
/**
|
||||
* Getter for the connection attachment.
|
||||
*
|
||||
* @param <T> The type of the attachment
|
||||
* @return Returns the user attachment
|
||||
* @since 1.3.7
|
||||
**/
|
||||
<T> T getAttachment();
|
||||
|
||||
/**
|
||||
* Does this websocket use an encrypted (wss/ssl) or unencrypted (ws) connection
|
||||
*
|
||||
* @return true, if the websocket does use wss and therefore has a SSLSession
|
||||
* @since 1.4.1
|
||||
*/
|
||||
boolean hasSSLSupport();
|
||||
|
||||
/**
|
||||
* Returns the ssl session of websocket, if ssl/wss is used for this instance.
|
||||
*
|
||||
* @return the ssl session of this websocket instance
|
||||
* @throws IllegalArgumentException the underlying channel does not use ssl (use hasSSLSupport()
|
||||
* to check)
|
||||
* @since 1.4.1
|
||||
*/
|
||||
SSLSession getSSLSession() throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Returns the used Sec-WebSocket-Protocol for this websocket connection
|
||||
*
|
||||
* @return the Sec-WebSocket-Protocol or null, if no draft available
|
||||
* @throws IllegalArgumentException the underlying draft does not support a Sec-WebSocket-Protocol
|
||||
* @since 1.5.2
|
||||
*/
|
||||
IProtocol getProtocol();
|
||||
}
|
||||
113
src/main/java/org/java_websocket/WebSocketAdapter.java
Normal file
113
src/main/java/org/java_websocket/WebSocketAdapter.java
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import org.java_websocket.drafts.Draft;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.framing.PingFrame;
|
||||
import org.java_websocket.framing.PongFrame;
|
||||
import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.handshake.HandshakeImpl1Server;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.java_websocket.handshake.ServerHandshakeBuilder;
|
||||
|
||||
/**
|
||||
* This class default implements all methods of the WebSocketListener that can be overridden
|
||||
* optionally when advances functionalities is needed.<br>
|
||||
**/
|
||||
public abstract class WebSocketAdapter implements WebSocketListener {
|
||||
|
||||
private PingFrame pingFrame;
|
||||
|
||||
/**
|
||||
* This default implementation does not do anything. Go ahead and overwrite it.
|
||||
*
|
||||
* @see WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket,
|
||||
* Draft, ClientHandshake)
|
||||
*/
|
||||
@Override
|
||||
public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
|
||||
ClientHandshake request) throws InvalidDataException {
|
||||
return new HandshakeImpl1Server();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request,
|
||||
ServerHandshake response) throws InvalidDataException {
|
||||
//To overwrite
|
||||
}
|
||||
|
||||
/**
|
||||
* This default implementation does not do anything which will cause the connections to always
|
||||
* progress.
|
||||
*
|
||||
* @see WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket,
|
||||
* ClientHandshake)
|
||||
*/
|
||||
@Override
|
||||
public void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request)
|
||||
throws InvalidDataException {
|
||||
//To overwrite
|
||||
}
|
||||
|
||||
/**
|
||||
* This default implementation will send a pong in response to the received ping. The pong frame
|
||||
* will have the same payload as the ping frame.
|
||||
*
|
||||
* @see WebSocketListener#onWebsocketPing(WebSocket, Framedata)
|
||||
*/
|
||||
@Override
|
||||
public void onWebsocketPing(WebSocket conn, Framedata f) {
|
||||
conn.sendFrame(new PongFrame((PingFrame) f));
|
||||
}
|
||||
|
||||
/**
|
||||
* This default implementation does not do anything. Go ahead and overwrite it.
|
||||
*
|
||||
* @see WebSocketListener#onWebsocketPong(WebSocket, Framedata)
|
||||
*/
|
||||
@Override
|
||||
public void onWebsocketPong(WebSocket conn, Framedata f) {
|
||||
//To overwrite
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation for onPreparePing, returns a (cached) PingFrame that has no application
|
||||
* data.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> connection from which the ping frame will be sent.
|
||||
* @return PingFrame to be sent.
|
||||
* @see WebSocketListener#onPreparePing(WebSocket)
|
||||
*/
|
||||
@Override
|
||||
public PingFrame onPreparePing(WebSocket conn) {
|
||||
if (pingFrame == null) {
|
||||
pingFrame = new PingFrame();
|
||||
}
|
||||
return pingFrame;
|
||||
}
|
||||
}
|
||||
51
src/main/java/org/java_websocket/WebSocketFactory.java
Normal file
51
src/main/java/org/java_websocket/WebSocketFactory.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.util.List;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
|
||||
public interface WebSocketFactory {
|
||||
|
||||
/**
|
||||
* Create a new Websocket with the provided listener, drafts and socket
|
||||
*
|
||||
* @param a The Listener for the WebsocketImpl
|
||||
* @param d The draft which should be used
|
||||
* @return A WebsocketImpl
|
||||
*/
|
||||
WebSocket createWebSocket(WebSocketAdapter a, Draft d);
|
||||
|
||||
/**
|
||||
* Create a new Websocket with the provided listener, drafts and socket
|
||||
*
|
||||
* @param a The Listener for the WebsocketImpl
|
||||
* @param drafts The drafts which should be used
|
||||
* @return A WebsocketImpl
|
||||
*/
|
||||
WebSocket createWebSocket(WebSocketAdapter a, List<Draft> drafts);
|
||||
|
||||
}
|
||||
915
src/main/java/org/java_websocket/WebSocketImpl.java
Normal file
915
src/main/java/org/java_websocket/WebSocketImpl.java
Normal file
|
|
@ -0,0 +1,915 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
import org.java_websocket.drafts.Draft_6455;
|
||||
import org.java_websocket.enums.CloseHandshakeType;
|
||||
import org.java_websocket.enums.HandshakeState;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.enums.ReadyState;
|
||||
import org.java_websocket.enums.Role;
|
||||
import org.java_websocket.exceptions.IncompleteHandshakeException;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidHandshakeException;
|
||||
import org.java_websocket.exceptions.LimitExceededException;
|
||||
import org.java_websocket.exceptions.WebsocketNotConnectedException;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.framing.PingFrame;
|
||||
import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.handshake.ClientHandshakeBuilder;
|
||||
import org.java_websocket.handshake.Handshakedata;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.java_websocket.handshake.ServerHandshakeBuilder;
|
||||
import org.java_websocket.interfaces.ISSLChannel;
|
||||
import org.java_websocket.protocols.IProtocol;
|
||||
import org.java_websocket.server.WebSocketServer.WebSocketWorker;
|
||||
import org.java_websocket.util.Charsetfunctions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents one end (client or server) of a single WebSocketImpl connection. Takes care of the
|
||||
* "handshake" phase, then allows for easy sending of text frames, and receiving frames through an
|
||||
* event-based model.
|
||||
*/
|
||||
public class WebSocketImpl implements WebSocket {
|
||||
|
||||
/**
|
||||
* The default port of WebSockets, as defined in the spec. If the nullary constructor is used,
|
||||
* DEFAULT_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024
|
||||
* usually require root permissions.
|
||||
*/
|
||||
public static final int DEFAULT_PORT = 80;
|
||||
|
||||
/**
|
||||
* The default wss port of WebSockets, as defined in the spec. If the nullary constructor is used,
|
||||
* DEFAULT_WSS_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024
|
||||
* usually require root permissions.
|
||||
*/
|
||||
public static final int DEFAULT_WSS_PORT = 443;
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
private final Logger log = LoggerFactory.getLogger(WebSocketImpl.class);
|
||||
|
||||
/**
|
||||
* Queue of buffers that need to be sent to the client.
|
||||
*/
|
||||
public final BlockingQueue<ByteBuffer> outQueue;
|
||||
/**
|
||||
* Queue of buffers that need to be processed
|
||||
*/
|
||||
public final BlockingQueue<ByteBuffer> inQueue;
|
||||
/**
|
||||
* The listener to notify of WebSocket events.
|
||||
*/
|
||||
private final WebSocketListener wsl;
|
||||
|
||||
private SelectionKey key;
|
||||
|
||||
/**
|
||||
* the possibly wrapped channel object whose selection is controlled by {@link #key}
|
||||
*/
|
||||
private ByteChannel channel;
|
||||
/**
|
||||
* Helper variable meant to store the thread which ( exclusively ) triggers this objects decode
|
||||
* method.
|
||||
**/
|
||||
private WebSocketWorker workerThread;
|
||||
/**
|
||||
* When true no further frames may be submitted to be sent
|
||||
*/
|
||||
private boolean flushandclosestate = false;
|
||||
|
||||
/**
|
||||
* The current state of the connection
|
||||
*/
|
||||
private volatile ReadyState readyState = ReadyState.NOT_YET_CONNECTED;
|
||||
|
||||
/**
|
||||
* A list of drafts available for this websocket
|
||||
*/
|
||||
private List<Draft> knownDrafts;
|
||||
|
||||
/**
|
||||
* The draft which is used by this websocket
|
||||
*/
|
||||
private Draft draft = null;
|
||||
|
||||
/**
|
||||
* The role which this websocket takes in the connection
|
||||
*/
|
||||
private Role role;
|
||||
|
||||
/**
|
||||
* the bytes of an incomplete received handshake
|
||||
*/
|
||||
private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate(0);
|
||||
|
||||
/**
|
||||
* stores the handshake sent by this websocket ( Role.CLIENT only )
|
||||
*/
|
||||
private ClientHandshake handshakerequest = null;
|
||||
|
||||
private String closemessage = null;
|
||||
private Integer closecode = null;
|
||||
private Boolean closedremotely = null;
|
||||
|
||||
private String resourceDescriptor = null;
|
||||
|
||||
/**
|
||||
* Attribute, when the last pong was received
|
||||
*/
|
||||
private long lastPong = System.nanoTime();
|
||||
|
||||
/**
|
||||
* Attribut to synchronize the write
|
||||
*/
|
||||
private final Object synchronizeWriteObject = new Object();
|
||||
|
||||
/**
|
||||
* Attribute to store connection attachment
|
||||
*
|
||||
* @since 1.3.7
|
||||
*/
|
||||
private Object attachment;
|
||||
|
||||
/**
|
||||
* Creates a websocket with server role
|
||||
*
|
||||
* @param listener The listener for this instance
|
||||
* @param drafts The drafts which should be used
|
||||
*/
|
||||
public WebSocketImpl(WebSocketListener listener, List<Draft> drafts) {
|
||||
this(listener, (Draft) null);
|
||||
this.role = Role.SERVER;
|
||||
// draft.copyInstance will be called when the draft is first needed
|
||||
if (drafts == null || drafts.isEmpty()) {
|
||||
knownDrafts = new ArrayList<>();
|
||||
knownDrafts.add(new Draft_6455());
|
||||
} else {
|
||||
knownDrafts = drafts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a websocket with client role
|
||||
*
|
||||
* @param listener The listener for this instance
|
||||
* @param draft The draft which should be used
|
||||
*/
|
||||
public WebSocketImpl(WebSocketListener listener, Draft draft) {
|
||||
// socket can be null because we want do be able to create the object without already having a bound channel
|
||||
if (listener == null || (draft == null && role == Role.SERVER)) {
|
||||
throw new IllegalArgumentException("parameters must not be null");
|
||||
}
|
||||
this.outQueue = new LinkedBlockingQueue<>();
|
||||
inQueue = new LinkedBlockingQueue<>();
|
||||
this.wsl = listener;
|
||||
this.role = Role.CLIENT;
|
||||
if (draft != null) {
|
||||
this.draft = draft.copyInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to decode the provided ByteBuffer
|
||||
*
|
||||
* @param socketBuffer the ByteBuffer to decode
|
||||
*/
|
||||
public void decode(ByteBuffer socketBuffer) {
|
||||
assert (socketBuffer.hasRemaining());
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("process({}): ({})", socketBuffer.remaining(),
|
||||
(socketBuffer.remaining() > 1000 ? "too big to display"
|
||||
: new String(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining())));
|
||||
}
|
||||
if (readyState != ReadyState.NOT_YET_CONNECTED) {
|
||||
if (readyState == ReadyState.OPEN) {
|
||||
decodeFrames(socketBuffer);
|
||||
}
|
||||
} else {
|
||||
if (decodeHandshake(socketBuffer) && (!isClosing() && !isClosed())) {
|
||||
assert (tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer
|
||||
.hasRemaining()); // the buffers will never have remaining bytes at the same time
|
||||
if (socketBuffer.hasRemaining()) {
|
||||
decodeFrames(socketBuffer);
|
||||
} else if (tmpHandshakeBytes.hasRemaining()) {
|
||||
decodeFrames(tmpHandshakeBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the handshake phase has is completed. In case of a broken handshake this will
|
||||
* be never the case.
|
||||
**/
|
||||
private boolean decodeHandshake(ByteBuffer socketBufferNew) {
|
||||
ByteBuffer socketBuffer;
|
||||
if (tmpHandshakeBytes.capacity() == 0) {
|
||||
socketBuffer = socketBufferNew;
|
||||
} else {
|
||||
if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) {
|
||||
ByteBuffer buf = ByteBuffer
|
||||
.allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining());
|
||||
tmpHandshakeBytes.flip();
|
||||
buf.put(tmpHandshakeBytes);
|
||||
tmpHandshakeBytes = buf;
|
||||
}
|
||||
|
||||
tmpHandshakeBytes.put(socketBufferNew);
|
||||
tmpHandshakeBytes.flip();
|
||||
socketBuffer = tmpHandshakeBytes;
|
||||
}
|
||||
socketBuffer.mark();
|
||||
try {
|
||||
HandshakeState handshakestate;
|
||||
try {
|
||||
if (role == Role.SERVER) {
|
||||
if (draft == null) {
|
||||
for (Draft d : knownDrafts) {
|
||||
d = d.copyInstance();
|
||||
try {
|
||||
d.setParseMode(role);
|
||||
socketBuffer.reset();
|
||||
Handshakedata tmphandshake = d.translateHandshake(socketBuffer);
|
||||
if (!(tmphandshake instanceof ClientHandshake)) {
|
||||
log.trace("Closing due to wrong handshake");
|
||||
closeConnectionDueToWrongHandshake(
|
||||
new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function"));
|
||||
return false;
|
||||
}
|
||||
ClientHandshake handshake = (ClientHandshake) tmphandshake;
|
||||
handshakestate = d.acceptHandshakeAsServer(handshake);
|
||||
if (handshakestate == HandshakeState.MATCHED) {
|
||||
resourceDescriptor = handshake.getResourceDescriptor();
|
||||
ServerHandshakeBuilder response;
|
||||
try {
|
||||
response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake);
|
||||
} catch (InvalidDataException e) {
|
||||
log.trace("Closing due to wrong handshake. Possible handshake rejection", e);
|
||||
closeConnectionDueToWrongHandshake(e);
|
||||
return false;
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Closing due to internal server error", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
closeConnectionDueToInternalServerError(e);
|
||||
return false;
|
||||
}
|
||||
write(d.createHandshake(
|
||||
d.postProcessHandshakeResponseAsServer(handshake, response)));
|
||||
draft = d;
|
||||
open(handshake);
|
||||
return true;
|
||||
}
|
||||
} catch (InvalidHandshakeException e) {
|
||||
// go on with an other draft
|
||||
}
|
||||
}
|
||||
if (draft == null) {
|
||||
log.trace("Closing due to protocol error: no draft matches");
|
||||
closeConnectionDueToWrongHandshake(
|
||||
new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches"));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// special case for multiple step handshakes
|
||||
Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
|
||||
if (!(tmphandshake instanceof ClientHandshake)) {
|
||||
log.trace("Closing due to protocol error: wrong http function");
|
||||
flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
|
||||
return false;
|
||||
}
|
||||
ClientHandshake handshake = (ClientHandshake) tmphandshake;
|
||||
handshakestate = draft.acceptHandshakeAsServer(handshake);
|
||||
|
||||
if (handshakestate == HandshakeState.MATCHED) {
|
||||
open(handshake);
|
||||
return true;
|
||||
} else {
|
||||
log.trace("Closing due to protocol error: the handshake did finally not match");
|
||||
close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if (role == Role.CLIENT) {
|
||||
draft.setParseMode(role);
|
||||
Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
|
||||
if (!(tmphandshake instanceof ServerHandshake)) {
|
||||
log.trace("Closing due to protocol error: wrong http function");
|
||||
flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
|
||||
return false;
|
||||
}
|
||||
ServerHandshake handshake = (ServerHandshake) tmphandshake;
|
||||
handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake);
|
||||
if (handshakestate == HandshakeState.MATCHED) {
|
||||
try {
|
||||
wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake);
|
||||
} catch (InvalidDataException e) {
|
||||
log.trace("Closing due to invalid data exception. Possible handshake rejection", e);
|
||||
flushAndClose(e.getCloseCode(), e.getMessage(), false);
|
||||
return false;
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Closing since client was never connected", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false);
|
||||
return false;
|
||||
}
|
||||
open(handshake);
|
||||
return true;
|
||||
} else {
|
||||
log.trace("Closing due to protocol error: draft {} refuses handshake", draft);
|
||||
close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake");
|
||||
}
|
||||
}
|
||||
} catch (InvalidHandshakeException e) {
|
||||
log.trace("Closing due to invalid handshake", e);
|
||||
close(e);
|
||||
}
|
||||
} catch (IncompleteHandshakeException e) {
|
||||
if (tmpHandshakeBytes.capacity() == 0) {
|
||||
socketBuffer.reset();
|
||||
int newsize = e.getPreferredSize();
|
||||
if (newsize == 0) {
|
||||
newsize = socketBuffer.capacity() + 16;
|
||||
} else {
|
||||
assert (e.getPreferredSize() >= socketBuffer.remaining());
|
||||
}
|
||||
tmpHandshakeBytes = ByteBuffer.allocate(newsize);
|
||||
|
||||
tmpHandshakeBytes.put(socketBufferNew);
|
||||
// tmpHandshakeBytes.flip();
|
||||
} else {
|
||||
tmpHandshakeBytes.position(tmpHandshakeBytes.limit());
|
||||
tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void decodeFrames(ByteBuffer socketBuffer) {
|
||||
List<Framedata> frames;
|
||||
try {
|
||||
frames = draft.translateFrame(socketBuffer);
|
||||
for (Framedata f : frames) {
|
||||
log.trace("matched frame: {}", f);
|
||||
draft.processFrame(this, f);
|
||||
}
|
||||
} catch (LimitExceededException e) {
|
||||
if (e.getLimit() == Integer.MAX_VALUE) {
|
||||
log.error("Closing due to invalid size of frame", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
close(e);
|
||||
} catch (InvalidDataException e) {
|
||||
log.error("Closing due to invalid data in frame", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
close(e);
|
||||
} catch (VirtualMachineError | ThreadDeath | LinkageError e) {
|
||||
log.error("Got fatal error during frame processing");
|
||||
throw e;
|
||||
} catch (Error e) {
|
||||
log.error("Closing web socket due to an error during frame processing");
|
||||
Exception exception = new Exception(e);
|
||||
wsl.onWebsocketError(this, exception);
|
||||
String errorMessage = "Got error " + e.getClass().getName();
|
||||
close(CloseFrame.UNEXPECTED_CONDITION, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection if the received handshake was not correct
|
||||
*
|
||||
* @param exception the InvalidDataException causing this problem
|
||||
*/
|
||||
private void closeConnectionDueToWrongHandshake(InvalidDataException exception) {
|
||||
write(generateHttpResponseDueToError(404));
|
||||
flushAndClose(exception.getCloseCode(), exception.getMessage(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection if there was a server error by a RuntimeException
|
||||
*
|
||||
* @param exception the RuntimeException causing this problem
|
||||
*/
|
||||
private void closeConnectionDueToInternalServerError(RuntimeException exception) {
|
||||
write(generateHttpResponseDueToError(500));
|
||||
flushAndClose(CloseFrame.NEVER_CONNECTED, exception.getMessage(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a simple response for the corresponding endpoint to indicate some error
|
||||
*
|
||||
* @param errorCode the http error code
|
||||
* @return the complete response as ByteBuffer
|
||||
*/
|
||||
private ByteBuffer generateHttpResponseDueToError(int errorCode) {
|
||||
String errorCodeDescription;
|
||||
switch (errorCode) {
|
||||
case 404:
|
||||
errorCodeDescription = "404 WebSocket Upgrade Failure";
|
||||
break;
|
||||
case 500:
|
||||
default:
|
||||
errorCodeDescription = "500 Internal Server Error";
|
||||
}
|
||||
return ByteBuffer.wrap(Charsetfunctions.asciiBytes("HTTP/1.1 " + errorCodeDescription
|
||||
+ "\r\nContent-Type: text/html\r\nServer: TooTallNate Java-WebSocket\r\nContent-Length: "
|
||||
+ (48 + errorCodeDescription.length()) + "\r\n\r\n<html><head></head><body><h1>"
|
||||
+ errorCodeDescription + "</h1></body></html>"));
|
||||
}
|
||||
|
||||
public synchronized void close(int code, String message, boolean remote) {
|
||||
if (readyState != ReadyState.CLOSING && readyState != ReadyState.CLOSED) {
|
||||
if (readyState == ReadyState.OPEN) {
|
||||
if (code == CloseFrame.ABNORMAL_CLOSE) {
|
||||
assert (!remote);
|
||||
readyState = ReadyState.CLOSING;
|
||||
flushAndClose(code, message, false);
|
||||
return;
|
||||
}
|
||||
if (draft.getCloseHandshakeType() != CloseHandshakeType.NONE) {
|
||||
try {
|
||||
if (!remote) {
|
||||
try {
|
||||
wsl.onWebsocketCloseInitiated(this, code, message);
|
||||
} catch (RuntimeException e) {
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
}
|
||||
if (isOpen()) {
|
||||
CloseFrame closeFrame = new CloseFrame();
|
||||
closeFrame.setReason(message);
|
||||
closeFrame.setCode(code);
|
||||
closeFrame.isValid();
|
||||
sendFrame(closeFrame);
|
||||
}
|
||||
} catch (InvalidDataException e) {
|
||||
log.error("generated frame is invalid", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false);
|
||||
}
|
||||
}
|
||||
flushAndClose(code, message, remote);
|
||||
} else if (code == CloseFrame.FLASHPOLICY) {
|
||||
assert (remote);
|
||||
flushAndClose(CloseFrame.FLASHPOLICY, message, true);
|
||||
} else if (code == CloseFrame.PROTOCOL_ERROR) { // this endpoint found a PROTOCOL_ERROR
|
||||
flushAndClose(code, message, remote);
|
||||
} else {
|
||||
flushAndClose(CloseFrame.NEVER_CONNECTED, message, false);
|
||||
}
|
||||
readyState = ReadyState.CLOSING;
|
||||
tmpHandshakeBytes = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(int code, String message) {
|
||||
close(code, message, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will close the connection immediately without a proper close handshake. The code and the
|
||||
* message therefore won't be transferred over the wire also they will be forwarded to
|
||||
* onClose/onWebsocketClose.
|
||||
*
|
||||
* @param code the closing code
|
||||
* @param message the closing message
|
||||
* @param remote Indicates who "generated" <code>code</code>.<br>
|
||||
* <code>true</code> means that this endpoint received the <code>code</code> from
|
||||
* the other endpoint.<br> false means this endpoint decided to send the given
|
||||
* code,<br>
|
||||
* <code>remote</code> may also be true if this endpoint started the closing
|
||||
* handshake since the other endpoint may not simply echo the <code>code</code> but
|
||||
* close the connection the same time this endpoint does do but with an other
|
||||
* <code>code</code>. <br>
|
||||
**/
|
||||
public synchronized void closeConnection(int code, String message, boolean remote) {
|
||||
if (readyState == ReadyState.CLOSED) {
|
||||
return;
|
||||
}
|
||||
//Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the ReadyState manually
|
||||
if (readyState == ReadyState.OPEN) {
|
||||
if (code == CloseFrame.ABNORMAL_CLOSE) {
|
||||
readyState = ReadyState.CLOSING;
|
||||
}
|
||||
}
|
||||
if (key != null) {
|
||||
// key.attach( null ); //see issue #114
|
||||
key.cancel();
|
||||
}
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage() != null && e.getMessage().equals("Broken pipe")) {
|
||||
log.trace("Caught IOException: Broken pipe during closeConnection()", e);
|
||||
} else {
|
||||
log.error("Exception during channel.close()", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.wsl.onWebsocketClose(this, code, message, remote);
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
if (draft != null) {
|
||||
draft.reset();
|
||||
}
|
||||
handshakerequest = null;
|
||||
readyState = ReadyState.CLOSED;
|
||||
}
|
||||
|
||||
protected void closeConnection(int code, boolean remote) {
|
||||
closeConnection(code, "", remote);
|
||||
}
|
||||
|
||||
public void closeConnection() {
|
||||
if (closedremotely == null) {
|
||||
throw new IllegalStateException("this method must be used in conjunction with flushAndClose");
|
||||
}
|
||||
closeConnection(closecode, closemessage, closedremotely);
|
||||
}
|
||||
|
||||
public void closeConnection(int code, String message) {
|
||||
closeConnection(code, message, false);
|
||||
}
|
||||
|
||||
public synchronized void flushAndClose(int code, String message, boolean remote) {
|
||||
if (flushandclosestate) {
|
||||
return;
|
||||
}
|
||||
closecode = code;
|
||||
closemessage = message;
|
||||
closedremotely = remote;
|
||||
|
||||
flushandclosestate = true;
|
||||
|
||||
wsl.onWriteDemand(
|
||||
this); // ensures that all outgoing frames are flushed before closing the connection
|
||||
try {
|
||||
wsl.onWebsocketClosing(this, code, message, remote);
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Exception in onWebsocketClosing", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
if (draft != null) {
|
||||
draft.reset();
|
||||
}
|
||||
handshakerequest = null;
|
||||
}
|
||||
|
||||
public void eot() {
|
||||
if (readyState == ReadyState.NOT_YET_CONNECTED) {
|
||||
closeConnection(CloseFrame.NEVER_CONNECTED, true);
|
||||
} else if (flushandclosestate) {
|
||||
closeConnection(closecode, closemessage, closedremotely);
|
||||
} else if (draft.getCloseHandshakeType() == CloseHandshakeType.NONE) {
|
||||
closeConnection(CloseFrame.NORMAL, true);
|
||||
} else if (draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY) {
|
||||
if (role == Role.SERVER) {
|
||||
closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
|
||||
} else {
|
||||
closeConnection(CloseFrame.NORMAL, true);
|
||||
}
|
||||
} else {
|
||||
closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(int code) {
|
||||
close(code, "", false);
|
||||
}
|
||||
|
||||
public void close(InvalidDataException e) {
|
||||
close(e.getCloseCode(), e.getMessage(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Text data to the other end.
|
||||
*
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
@Override
|
||||
public void send(String text) {
|
||||
if (text == null) {
|
||||
throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
|
||||
}
|
||||
send(draft.createFrames(text, role == Role.CLIENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Binary data (plain bytes) to the other end.
|
||||
*
|
||||
* @throws IllegalArgumentException the data is null
|
||||
* @throws WebsocketNotConnectedException websocket is not yet connected
|
||||
*/
|
||||
@Override
|
||||
public void send(ByteBuffer bytes) {
|
||||
if (bytes == null) {
|
||||
throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
|
||||
}
|
||||
send(draft.createFrames(bytes, role == Role.CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(byte[] bytes) {
|
||||
send(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
private void send(Collection<Framedata> frames) {
|
||||
if (!isOpen()) {
|
||||
throw new WebsocketNotConnectedException();
|
||||
}
|
||||
if (frames == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
ArrayList<ByteBuffer> outgoingFrames = new ArrayList<>();
|
||||
for (Framedata f : frames) {
|
||||
log.trace("send frame: {}", f);
|
||||
outgoingFrames.add(draft.createBinaryFrame(f));
|
||||
}
|
||||
write(outgoingFrames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
|
||||
send(draft.continuousFrame(op, buffer, fin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFrame(Collection<Framedata> frames) {
|
||||
send(frames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFrame(Framedata framedata) {
|
||||
send(Collections.singletonList(framedata));
|
||||
}
|
||||
|
||||
public void sendPing() throws NullPointerException {
|
||||
// Gets a PingFrame from WebSocketListener(wsl) and sends it.
|
||||
PingFrame pingFrame = wsl.onPreparePing(this);
|
||||
if (pingFrame == null) {
|
||||
throw new NullPointerException(
|
||||
"onPreparePing(WebSocket) returned null. PingFrame to sent can't be null.");
|
||||
}
|
||||
sendFrame(pingFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBufferedData() {
|
||||
return !this.outQueue.isEmpty();
|
||||
}
|
||||
|
||||
public void startHandshake(ClientHandshakeBuilder handshakedata)
|
||||
throws InvalidHandshakeException {
|
||||
// Store the Handshake Request we are about to send
|
||||
this.handshakerequest = draft.postProcessHandshakeRequestAsClient(handshakedata);
|
||||
|
||||
resourceDescriptor = handshakedata.getResourceDescriptor();
|
||||
assert (resourceDescriptor != null);
|
||||
|
||||
// Notify Listener
|
||||
try {
|
||||
wsl.onWebsocketHandshakeSentAsClient(this, this.handshakerequest);
|
||||
} catch (InvalidDataException e) {
|
||||
// Stop if the client code throws an exception
|
||||
throw new InvalidHandshakeException("Handshake data rejected by client.");
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Exception in startHandshake", e);
|
||||
wsl.onWebsocketError(this, e);
|
||||
throw new InvalidHandshakeException("rejected because of " + e);
|
||||
}
|
||||
|
||||
// Send
|
||||
write(draft.createHandshake(this.handshakerequest));
|
||||
}
|
||||
|
||||
private void write(ByteBuffer buf) {
|
||||
log.trace("write({}): {}", buf.remaining(),
|
||||
buf.remaining() > 1000 ? "too big to display" : new String(buf.array()));
|
||||
|
||||
outQueue.add(buf);
|
||||
wsl.onWriteDemand(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a list of bytebuffer (frames in binary form) into the outgoing queue
|
||||
*
|
||||
* @param bufs the list of bytebuffer
|
||||
*/
|
||||
private void write(List<ByteBuffer> bufs) {
|
||||
synchronized (synchronizeWriteObject) {
|
||||
for (ByteBuffer b : bufs) {
|
||||
write(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void open(Handshakedata d) {
|
||||
log.trace("open using draft: {}", draft);
|
||||
readyState = ReadyState.OPEN;
|
||||
updateLastPong();
|
||||
try {
|
||||
wsl.onWebsocketOpen(this, d);
|
||||
} catch (RuntimeException e) {
|
||||
wsl.onWebsocketError(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return readyState == ReadyState.OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosing() {
|
||||
return readyState == ReadyState.CLOSING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlushAndClose() {
|
||||
return flushandclosestate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return readyState == ReadyState.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadyState getReadyState() {
|
||||
return readyState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the selection key of this implementation
|
||||
*/
|
||||
public void setSelectionKey(SelectionKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the selection key of this implementation
|
||||
*/
|
||||
public SelectionKey getSelectionKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString(); // its nice to be able to set breakpoints here
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteSocketAddress() {
|
||||
return wsl.getRemoteSocketAddress(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalSocketAddress() {
|
||||
return wsl.getLocalSocketAddress(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Draft getDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
close(CloseFrame.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceDescriptor() {
|
||||
return resourceDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the last pong received
|
||||
*
|
||||
* @return the timestamp for the last received pong
|
||||
*/
|
||||
long getLastPong() {
|
||||
return lastPong;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the timestamp when the last pong was received
|
||||
*/
|
||||
public void updateLastPong() {
|
||||
this.lastPong = System.nanoTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the websocket listener
|
||||
*
|
||||
* @return the websocket listener associated with this instance
|
||||
*/
|
||||
public WebSocketListener getWebSocketListener() {
|
||||
return wsl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttachment() {
|
||||
return (T) attachment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSSLSupport() {
|
||||
return channel instanceof ISSLChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSSLSession() {
|
||||
if (!hasSSLSupport()) {
|
||||
throw new IllegalArgumentException(
|
||||
"This websocket uses ws instead of wss. No SSLSession available.");
|
||||
}
|
||||
return ((ISSLChannel) channel).getSSLEngine().getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IProtocol getProtocol() {
|
||||
if (draft == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(draft instanceof Draft_6455)) {
|
||||
throw new IllegalArgumentException("This draft does not support Sec-WebSocket-Protocol");
|
||||
}
|
||||
return ((Draft_6455) draft).getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setAttachment(T attachment) {
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
public ByteChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(ByteChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public WebSocketWorker getWorkerThread() {
|
||||
return workerThread;
|
||||
}
|
||||
|
||||
public void setWorkerThread(WebSocketWorker workerThread) {
|
||||
this.workerThread = workerThread;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
200
src/main/java/org/java_websocket/WebSocketListener.java
Normal file
200
src/main/java/org/java_websocket/WebSocketListener.java
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.framing.PingFrame;
|
||||
import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.handshake.Handshakedata;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.java_websocket.handshake.ServerHandshakeBuilder;
|
||||
|
||||
/**
|
||||
* Implemented by <code>WebSocketClient</code> and <code>WebSocketServer</code>. The methods within are
|
||||
* called by <code>WebSocket</code>. Almost every method takes a first parameter conn which represents
|
||||
* the source of the respective event.
|
||||
*/
|
||||
public interface WebSocketListener {
|
||||
|
||||
/**
|
||||
* Called on the server side when the socket connection is first established, and the WebSocket
|
||||
* handshake has been received. This method allows to deny connections based on the received
|
||||
* handshake.<br> By default this method only requires protocol compliance.
|
||||
*
|
||||
* @param conn The WebSocket related to this event
|
||||
* @param draft The protocol draft the client uses to connect
|
||||
* @param request The opening http message send by the client. Can be used to access additional
|
||||
* fields like cookies.
|
||||
* @return Returns an incomplete handshake containing all optional fields
|
||||
* @throws InvalidDataException Throwing this exception will cause this handshake to be rejected
|
||||
*/
|
||||
ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
|
||||
ClientHandshake request) throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Called on the client side when the socket connection is first established, and the
|
||||
* WebSocketImpl handshake response has been received.
|
||||
*
|
||||
* @param conn The WebSocket related to this event
|
||||
* @param request The handshake initially send out to the server by this websocket.
|
||||
* @param response The handshake the server sent in response to the request.
|
||||
* @throws InvalidDataException Allows the client to reject the connection with the server in
|
||||
* respect of its handshake response.
|
||||
*/
|
||||
void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request,
|
||||
ServerHandshake response) throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Called on the client side when the socket connection is first established, and the
|
||||
* WebSocketImpl handshake has just been sent.
|
||||
*
|
||||
* @param conn The WebSocket related to this event
|
||||
* @param request The handshake sent to the server by this websocket
|
||||
* @throws InvalidDataException Allows the client to stop the connection from progressing
|
||||
*/
|
||||
void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request)
|
||||
throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Called when an entire text frame has been received. Do whatever you want here...
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param message The UTF-8 decoded message that was received.
|
||||
*/
|
||||
void onWebsocketMessage(WebSocket conn, String message);
|
||||
|
||||
/**
|
||||
* Called when an entire binary frame has been received. Do whatever you want here...
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param blob The binary message that was received.
|
||||
*/
|
||||
void onWebsocketMessage(WebSocket conn, ByteBuffer blob);
|
||||
|
||||
/**
|
||||
* Called after <var>onHandshakeReceived</var> returns <var>true</var>. Indicates that a complete
|
||||
* WebSocket connection has been established, and we are ready to send/receive data.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param d The handshake of the websocket instance
|
||||
*/
|
||||
void onWebsocketOpen(WebSocket conn, Handshakedata d);
|
||||
|
||||
/**
|
||||
* Called after <code>WebSocket#close</code> is explicity called, or when the other end of the
|
||||
* WebSocket connection is closed.
|
||||
*
|
||||
* @param ws The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param code The codes can be looked up here: {@link CloseFrame}
|
||||
* @param reason Additional information string
|
||||
* @param remote Returns whether or not the closing of the connection was initiated by the remote
|
||||
* host.
|
||||
*/
|
||||
void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote);
|
||||
|
||||
/**
|
||||
* Called as soon as no further frames are accepted
|
||||
*
|
||||
* @param ws The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param code The codes can be looked up here: {@link CloseFrame}
|
||||
* @param reason Additional information string
|
||||
* @param remote Returns whether or not the closing of the connection was initiated by the remote
|
||||
* host.
|
||||
*/
|
||||
void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote);
|
||||
|
||||
/**
|
||||
* send when this peer sends a close handshake
|
||||
*
|
||||
* @param ws The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param code The codes can be looked up here: {@link CloseFrame}
|
||||
* @param reason Additional information string
|
||||
*/
|
||||
void onWebsocketCloseInitiated(WebSocket ws, int code, String reason);
|
||||
|
||||
/**
|
||||
* Called if an exception worth noting occurred. If an error causes the connection to fail onClose
|
||||
* will be called additionally afterwards.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param ex The exception that occurred. <br> Might be null if the exception is not related to
|
||||
* any specific connection. For example if the server port could not be bound.
|
||||
*/
|
||||
void onWebsocketError(WebSocket conn, Exception ex);
|
||||
|
||||
/**
|
||||
* Called a ping frame has been received. This method must send a corresponding pong by itself.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param f The ping frame. Control frames may contain payload.
|
||||
*/
|
||||
void onWebsocketPing(WebSocket conn, Framedata f);
|
||||
|
||||
/**
|
||||
* Called just before a ping frame is sent, in order to allow users to customize their ping frame
|
||||
* data.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> connection from which the ping frame will be sent.
|
||||
* @return PingFrame to be sent.
|
||||
*/
|
||||
PingFrame onPreparePing(WebSocket conn);
|
||||
|
||||
/**
|
||||
* Called when a pong frame is received.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @param f The pong frame. Control frames may contain payload.
|
||||
**/
|
||||
void onWebsocketPong(WebSocket conn, Framedata f);
|
||||
|
||||
/**
|
||||
* This method is used to inform the selector thread that there is data queued to be written to
|
||||
* the socket.
|
||||
*
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
*/
|
||||
void onWriteDemand(WebSocket conn);
|
||||
|
||||
/**
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @return Returns the address of the endpoint this socket is bound to.
|
||||
* @see WebSocket#getLocalSocketAddress()
|
||||
*/
|
||||
InetSocketAddress getLocalSocketAddress(WebSocket conn);
|
||||
|
||||
/**
|
||||
* @param conn The <code>WebSocket</code> instance this event is occurring on.
|
||||
* @return Returns the address of the endpoint this socket is connected to, or{@code null} if it
|
||||
* is unconnected.
|
||||
* @see WebSocket#getRemoteSocketAddress()
|
||||
*/
|
||||
InetSocketAddress getRemoteSocketAddress(WebSocket conn);
|
||||
}
|
||||
61
src/main/java/org/java_websocket/WebSocketServerFactory.java
Normal file
61
src/main/java/org/java_websocket/WebSocketServerFactory.java
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.List;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
|
||||
/**
|
||||
* Interface to encapsulate the required methods for a websocket factory
|
||||
*/
|
||||
public interface WebSocketServerFactory extends WebSocketFactory {
|
||||
|
||||
@Override
|
||||
WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d);
|
||||
|
||||
@Override
|
||||
WebSocketImpl createWebSocket(WebSocketAdapter a, List<Draft> drafts);
|
||||
|
||||
/**
|
||||
* Allows to wrap the SocketChannel( key.channel() ) to insert a protocol layer( like ssl or proxy
|
||||
* authentication) beyond the ws layer.
|
||||
*
|
||||
* @param channel The SocketChannel to wrap
|
||||
* @param key a SelectionKey of an open SocketChannel.
|
||||
* @return The channel on which the read and write operations will be performed.<br>
|
||||
* @throws IOException may be thrown while writing on the channel
|
||||
*/
|
||||
ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException;
|
||||
|
||||
/**
|
||||
* Allows to shutdown the websocket factory for a clean shutdown
|
||||
*/
|
||||
void close();
|
||||
}
|
||||
76
src/main/java/org/java_websocket/WrappedByteChannel.java
Normal file
76
src/main/java/org/java_websocket/WrappedByteChannel.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
|
||||
public interface WrappedByteChannel extends ByteChannel {
|
||||
|
||||
/**
|
||||
* returns whether writeMore should be called write additional data.
|
||||
*
|
||||
* @return is a additional write needed
|
||||
*/
|
||||
boolean isNeedWrite();
|
||||
|
||||
/**
|
||||
* Gets called when {@link #isNeedWrite()} ()} requires a additional rite
|
||||
*
|
||||
* @throws IOException may be thrown due to an error while writing
|
||||
*/
|
||||
void writeMore() throws IOException;
|
||||
|
||||
/**
|
||||
* returns whether readMore should be called to fetch data which has been decoded but not yet been
|
||||
* returned.
|
||||
*
|
||||
* @return is a additional read needed
|
||||
* @see #read(ByteBuffer)
|
||||
* @see #readMore(ByteBuffer)
|
||||
**/
|
||||
boolean isNeedRead();
|
||||
|
||||
/**
|
||||
* This function does not read data from the underlying channel at all. It is just a way to fetch
|
||||
* data which has already be received or decoded but was but was not yet returned to the user.
|
||||
* This could be the case when the decoded data did not fit into the buffer the user passed to
|
||||
* {@link #read(ByteBuffer)}.
|
||||
*
|
||||
* @param dst the destiny of the read
|
||||
* @return the amount of remaining data
|
||||
* @throws IOException when a error occurred during unwrapping
|
||||
**/
|
||||
int readMore(ByteBuffer dst) throws IOException;
|
||||
|
||||
/**
|
||||
* This function returns the blocking state of the channel
|
||||
*
|
||||
* @return is the channel blocking
|
||||
*/
|
||||
boolean isBlocking();
|
||||
}
|
||||
51
src/main/java/org/java_websocket/client/DnsResolver.java
Normal file
51
src/main/java/org/java_websocket/client/DnsResolver.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.client;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Users may implement this interface to override the default DNS lookup offered by the OS.
|
||||
*
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public interface DnsResolver {
|
||||
|
||||
/**
|
||||
* Resolves the IP address for the given URI.
|
||||
* <p>
|
||||
* This method should never return null. If it's not able to resolve the IP address then it should
|
||||
* throw an UnknownHostException
|
||||
*
|
||||
* @param uri The URI to be resolved
|
||||
* @return The resolved IP address
|
||||
* @throws UnknownHostException if no IP address for the <code>uri</code> could be found.
|
||||
*/
|
||||
InetAddress resolve(URI uri) throws UnknownHostException;
|
||||
|
||||
}
|
||||
1025
src/main/java/org/java_websocket/client/WebSocketClient.java
Normal file
1025
src/main/java/org/java_websocket/client/WebSocketClient.java
Normal file
File diff suppressed because it is too large
Load Diff
29
src/main/java/org/java_websocket/client/package-info.java
Normal file
29
src/main/java/org/java_websocket/client/package-info.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all implementations in relation with the WebSocketClient.
|
||||
*/
|
||||
package org.java_websocket.client;
|
||||
355
src/main/java/org/java_websocket/drafts/Draft.java
Normal file
355
src/main/java/org/java_websocket/drafts/Draft.java
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.drafts;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.java_websocket.WebSocketImpl;
|
||||
import org.java_websocket.enums.CloseHandshakeType;
|
||||
import org.java_websocket.enums.HandshakeState;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.enums.Role;
|
||||
import org.java_websocket.exceptions.IncompleteHandshakeException;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidHandshakeException;
|
||||
import org.java_websocket.framing.BinaryFrame;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.framing.ContinuousFrame;
|
||||
import org.java_websocket.framing.DataFrame;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.framing.TextFrame;
|
||||
import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.handshake.ClientHandshakeBuilder;
|
||||
import org.java_websocket.handshake.HandshakeBuilder;
|
||||
import org.java_websocket.handshake.HandshakeImpl1Client;
|
||||
import org.java_websocket.handshake.HandshakeImpl1Server;
|
||||
import org.java_websocket.handshake.Handshakedata;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.java_websocket.handshake.ServerHandshakeBuilder;
|
||||
import org.java_websocket.util.Charsetfunctions;
|
||||
|
||||
/**
|
||||
* Base class for everything of a websocket specification which is not common such as the way the
|
||||
* handshake is read or frames are transferred.
|
||||
**/
|
||||
public abstract class Draft {
|
||||
|
||||
/**
|
||||
* In some cases the handshake will be parsed different depending on whether
|
||||
*/
|
||||
protected Role role = null;
|
||||
|
||||
protected Opcode continuousFrameType = null;
|
||||
|
||||
public static ByteBuffer readLine(ByteBuffer buf) {
|
||||
ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining());
|
||||
byte prev;
|
||||
byte cur = '0';
|
||||
while (buf.hasRemaining()) {
|
||||
prev = cur;
|
||||
cur = buf.get();
|
||||
sbuf.put(cur);
|
||||
if (prev == (byte) '\r' && cur == (byte) '\n') {
|
||||
sbuf.limit(sbuf.position() - 2);
|
||||
sbuf.position(0);
|
||||
return sbuf;
|
||||
|
||||
}
|
||||
}
|
||||
// ensure that there wont be any bytes skipped
|
||||
buf.position(buf.position() - sbuf.position());
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String readStringLine(ByteBuffer buf) {
|
||||
ByteBuffer b = readLine(buf);
|
||||
return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit());
|
||||
}
|
||||
|
||||
public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, Role role)
|
||||
throws InvalidHandshakeException {
|
||||
HandshakeBuilder handshake;
|
||||
|
||||
String line = readStringLine(buf);
|
||||
if (line == null) {
|
||||
throw new IncompleteHandshakeException(buf.capacity() + 128);
|
||||
}
|
||||
|
||||
String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101 Switching the Protocols
|
||||
if (firstLineTokens.length != 3) {
|
||||
throw new InvalidHandshakeException();
|
||||
}
|
||||
if (role == Role.CLIENT) {
|
||||
handshake = translateHandshakeHttpClient(firstLineTokens, line);
|
||||
} else {
|
||||
handshake = translateHandshakeHttpServer(firstLineTokens, line);
|
||||
}
|
||||
line = readStringLine(buf);
|
||||
while (line != null && line.length() > 0) {
|
||||
String[] pair = line.split(":", 2);
|
||||
if (pair.length != 2) {
|
||||
throw new InvalidHandshakeException("not an http header");
|
||||
}
|
||||
// If the handshake contains already a specific key, append the new value
|
||||
if (handshake.hasFieldValue(pair[0])) {
|
||||
handshake.put(pair[0],
|
||||
handshake.getFieldValue(pair[0]) + "; " + pair[1].replaceFirst("^ +", ""));
|
||||
} else {
|
||||
handshake.put(pair[0], pair[1].replaceFirst("^ +", ""));
|
||||
}
|
||||
line = readStringLine(buf);
|
||||
}
|
||||
if (line == null) {
|
||||
throw new IncompleteHandshakeException();
|
||||
}
|
||||
return handshake;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking the handshake for the role as server
|
||||
*
|
||||
* @param firstLineTokens the token of the first line split as as an string array
|
||||
* @param line the whole line
|
||||
* @return a handshake
|
||||
*/
|
||||
private static HandshakeBuilder translateHandshakeHttpServer(String[] firstLineTokens,
|
||||
String line) throws InvalidHandshakeException {
|
||||
// translating/parsing the request from the CLIENT
|
||||
if (!"GET".equalsIgnoreCase(firstLineTokens[0])) {
|
||||
throw new InvalidHandshakeException(String
|
||||
.format("Invalid request method received: %s Status line: %s", firstLineTokens[0], line));
|
||||
}
|
||||
if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[2])) {
|
||||
throw new InvalidHandshakeException(String
|
||||
.format("Invalid status line received: %s Status line: %s", firstLineTokens[2], line));
|
||||
}
|
||||
ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
|
||||
clienthandshake.setResourceDescriptor(firstLineTokens[1]);
|
||||
return clienthandshake;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking the handshake for the role as client
|
||||
*
|
||||
* @param firstLineTokens the token of the first line split as as an string array
|
||||
* @param line the whole line
|
||||
* @return a handshake
|
||||
*/
|
||||
private static HandshakeBuilder translateHandshakeHttpClient(String[] firstLineTokens,
|
||||
String line) throws InvalidHandshakeException {
|
||||
// translating/parsing the response from the SERVER
|
||||
if (!"101".equals(firstLineTokens[1])) {
|
||||
throw new InvalidHandshakeException(String
|
||||
.format("Invalid status code received: %s Status line: %s", firstLineTokens[1], line));
|
||||
}
|
||||
if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[0])) {
|
||||
throw new InvalidHandshakeException(String
|
||||
.format("Invalid status line received: %s Status line: %s", firstLineTokens[0], line));
|
||||
}
|
||||
HandshakeBuilder handshake = new HandshakeImpl1Server();
|
||||
ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
|
||||
serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1]));
|
||||
serverhandshake.setHttpStatusMessage(firstLineTokens[2]);
|
||||
return handshake;
|
||||
}
|
||||
|
||||
public abstract HandshakeState acceptHandshakeAsClient(ClientHandshake request,
|
||||
ServerHandshake response) throws InvalidHandshakeException;
|
||||
|
||||
public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata)
|
||||
throws InvalidHandshakeException;
|
||||
|
||||
protected boolean basicAccept(Handshakedata handshakedata) {
|
||||
return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata
|
||||
.getFieldValue("Connection").toLowerCase(Locale.ENGLISH).contains("upgrade");
|
||||
}
|
||||
|
||||
public abstract ByteBuffer createBinaryFrame(Framedata framedata);
|
||||
|
||||
public abstract List<Framedata> createFrames(ByteBuffer binary, boolean mask);
|
||||
|
||||
public abstract List<Framedata> createFrames(String text, boolean mask);
|
||||
|
||||
|
||||
/**
|
||||
* Handle the frame specific to the draft
|
||||
*
|
||||
* @param webSocketImpl the websocketimpl used for this draft
|
||||
* @param frame the frame which is supposed to be handled
|
||||
* @throws InvalidDataException will be thrown on invalid data
|
||||
*/
|
||||
public abstract void processFrame(WebSocketImpl webSocketImpl, Framedata frame)
|
||||
throws InvalidDataException;
|
||||
|
||||
public List<Framedata> continuousFrame(Opcode op, ByteBuffer buffer, boolean fin) {
|
||||
if (op != Opcode.BINARY && op != Opcode.TEXT) {
|
||||
throw new IllegalArgumentException("Only Opcode.BINARY or Opcode.TEXT are allowed");
|
||||
}
|
||||
DataFrame bui = null;
|
||||
if (continuousFrameType != null) {
|
||||
bui = new ContinuousFrame();
|
||||
} else {
|
||||
continuousFrameType = op;
|
||||
if (op == Opcode.BINARY) {
|
||||
bui = new BinaryFrame();
|
||||
} else if (op == Opcode.TEXT) {
|
||||
bui = new TextFrame();
|
||||
}
|
||||
}
|
||||
bui.setPayload(buffer);
|
||||
bui.setFin(fin);
|
||||
try {
|
||||
bui.isValid();
|
||||
} catch (InvalidDataException e) {
|
||||
throw new IllegalArgumentException(
|
||||
e); // can only happen when one builds close frames(Opcode.Close)
|
||||
}
|
||||
if (fin) {
|
||||
continuousFrameType = null;
|
||||
} else {
|
||||
continuousFrameType = op;
|
||||
}
|
||||
return Collections.singletonList((Framedata) bui);
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
/**
|
||||
* @deprecated use createHandshake without the role
|
||||
*/
|
||||
@Deprecated
|
||||
public List<ByteBuffer> createHandshake(Handshakedata handshakedata, Role ownrole) {
|
||||
return createHandshake(handshakedata);
|
||||
}
|
||||
|
||||
public List<ByteBuffer> createHandshake(Handshakedata handshakedata) {
|
||||
return createHandshake(handshakedata, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use createHandshake without the role since it does not have any effect
|
||||
*/
|
||||
@Deprecated
|
||||
public List<ByteBuffer> createHandshake(Handshakedata handshakedata, Role ownrole,
|
||||
boolean withcontent) {
|
||||
return createHandshake(handshakedata, withcontent);
|
||||
}
|
||||
|
||||
public List<ByteBuffer> createHandshake(Handshakedata handshakedata, boolean withcontent) {
|
||||
StringBuilder bui = new StringBuilder(100);
|
||||
if (handshakedata instanceof ClientHandshake) {
|
||||
bui.append("GET ").append(((ClientHandshake) handshakedata).getResourceDescriptor())
|
||||
.append(" HTTP/1.1");
|
||||
} else if (handshakedata instanceof ServerHandshake) {
|
||||
bui.append("HTTP/1.1 101 ").append(((ServerHandshake) handshakedata).getHttpStatusMessage());
|
||||
} else {
|
||||
throw new IllegalArgumentException("unknown role");
|
||||
}
|
||||
bui.append("\r\n");
|
||||
Iterator<String> it = handshakedata.iterateHttpFields();
|
||||
while (it.hasNext()) {
|
||||
String fieldname = it.next();
|
||||
String fieldvalue = handshakedata.getFieldValue(fieldname);
|
||||
bui.append(fieldname);
|
||||
bui.append(": ");
|
||||
bui.append(fieldvalue);
|
||||
bui.append("\r\n");
|
||||
}
|
||||
bui.append("\r\n");
|
||||
byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString());
|
||||
|
||||
byte[] content = withcontent ? handshakedata.getContent() : null;
|
||||
ByteBuffer bytebuffer = ByteBuffer
|
||||
.allocate((content == null ? 0 : content.length) + httpheader.length);
|
||||
bytebuffer.put(httpheader);
|
||||
if (content != null) {
|
||||
bytebuffer.put(content);
|
||||
}
|
||||
bytebuffer.flip();
|
||||
return Collections.singletonList(bytebuffer);
|
||||
}
|
||||
|
||||
public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
|
||||
ClientHandshakeBuilder request) throws InvalidHandshakeException;
|
||||
|
||||
public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request,
|
||||
ServerHandshakeBuilder response) throws InvalidHandshakeException;
|
||||
|
||||
public abstract List<Framedata> translateFrame(ByteBuffer buffer) throws InvalidDataException;
|
||||
|
||||
public abstract CloseHandshakeType getCloseHandshakeType();
|
||||
|
||||
/**
|
||||
* Drafts must only be by one websocket at all. To prevent drafts to be used more than once the
|
||||
* Websocket implementation should call this method in order to create a new usable version of a
|
||||
* given draft instance.<br> The copy can be safely used in conjunction with a new websocket
|
||||
* connection.
|
||||
*
|
||||
* @return a copy of the draft
|
||||
*/
|
||||
public abstract Draft copyInstance();
|
||||
|
||||
public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException {
|
||||
return translateHandshakeHttp(buf, role);
|
||||
}
|
||||
|
||||
public int checkAlloc(int bytecount) throws InvalidDataException {
|
||||
if (bytecount < 0) {
|
||||
throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count");
|
||||
}
|
||||
return bytecount;
|
||||
}
|
||||
|
||||
int readVersion(Handshakedata handshakedata) {
|
||||
String vers = handshakedata.getFieldValue("Sec-WebSocket-Version");
|
||||
if (vers.length() > 0) {
|
||||
int v;
|
||||
try {
|
||||
v = Integer.parseInt(vers.trim());
|
||||
return v;
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setParseMode(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
1213
src/main/java/org/java_websocket/drafts/Draft_6455.java
Normal file
1213
src/main/java/org/java_websocket/drafts/Draft_6455.java
Normal file
File diff suppressed because it is too large
Load Diff
29
src/main/java/org/java_websocket/drafts/package-info.java
Normal file
29
src/main/java/org/java_websocket/drafts/package-info.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all implementations in relation with the WebSocket drafts.
|
||||
*/
|
||||
package org.java_websocket.drafts;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.java_websocket.enums;
|
||||
|
||||
/**
|
||||
* Enum which represents type of handshake is required for a close
|
||||
*/
|
||||
public enum CloseHandshakeType {
|
||||
NONE, ONEWAY, TWOWAY
|
||||
}
|
||||
15
src/main/java/org/java_websocket/enums/HandshakeState.java
Normal file
15
src/main/java/org/java_websocket/enums/HandshakeState.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package org.java_websocket.enums;
|
||||
|
||||
/**
|
||||
* Enum which represents the states a handshake may be in
|
||||
*/
|
||||
public enum HandshakeState {
|
||||
/**
|
||||
* Handshake matched this Draft successfully
|
||||
*/
|
||||
MATCHED,
|
||||
/**
|
||||
* Handshake is does not match this Draft
|
||||
*/
|
||||
NOT_MATCHED
|
||||
}
|
||||
9
src/main/java/org/java_websocket/enums/Opcode.java
Normal file
9
src/main/java/org/java_websocket/enums/Opcode.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package org.java_websocket.enums;
|
||||
|
||||
/**
|
||||
* Enum which contains the different valid opcodes
|
||||
*/
|
||||
public enum Opcode {
|
||||
CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
|
||||
// more to come
|
||||
}
|
||||
8
src/main/java/org/java_websocket/enums/ReadyState.java
Normal file
8
src/main/java/org/java_websocket/enums/ReadyState.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package org.java_websocket.enums;
|
||||
|
||||
/**
|
||||
* Enum which represents the state a websocket may be in
|
||||
*/
|
||||
public enum ReadyState {
|
||||
NOT_YET_CONNECTED, OPEN, CLOSING, CLOSED
|
||||
}
|
||||
8
src/main/java/org/java_websocket/enums/Role.java
Normal file
8
src/main/java/org/java_websocket/enums/Role.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package org.java_websocket.enums;
|
||||
|
||||
/**
|
||||
* Enum which represents the states a websocket may be in
|
||||
*/
|
||||
public enum Role {
|
||||
CLIENT, SERVER
|
||||
}
|
||||
29
src/main/java/org/java_websocket/enums/package-info.java
Normal file
29
src/main/java/org/java_websocket/enums/package-info.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all enums.
|
||||
*/
|
||||
package org.java_websocket.enums;
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
/**
|
||||
* Exception which indicates that the frame is not yet complete
|
||||
*/
|
||||
public class IncompleteException extends Exception {
|
||||
|
||||
/**
|
||||
* It's Serializable.
|
||||
*/
|
||||
private static final long serialVersionUID = 7330519489840500997L;
|
||||
|
||||
/**
|
||||
* The preferred size
|
||||
*/
|
||||
private final int preferredSize;
|
||||
|
||||
/**
|
||||
* Constructor for the preferred size of a frame
|
||||
*
|
||||
* @param preferredSize the preferred size of a frame
|
||||
*/
|
||||
public IncompleteException(int preferredSize) {
|
||||
this.preferredSize = preferredSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the preferredSize
|
||||
*
|
||||
* @return the value of the preferred size
|
||||
*/
|
||||
public int getPreferredSize() {
|
||||
return preferredSize;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
/**
|
||||
* exception which indicates that a incomplete handshake was received
|
||||
*/
|
||||
public class IncompleteHandshakeException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 7906596804233893092L;
|
||||
|
||||
/**
|
||||
* attribute which size of handshake would have been preferred
|
||||
*/
|
||||
private final int preferredSize;
|
||||
|
||||
/**
|
||||
* constructor for a IncompleteHandshakeException
|
||||
* <p>
|
||||
*
|
||||
* @param preferredSize the preferred size
|
||||
*/
|
||||
public IncompleteHandshakeException(int preferredSize) {
|
||||
this.preferredSize = preferredSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a IncompleteHandshakeException
|
||||
* <p>
|
||||
* preferredSize will be 0
|
||||
*/
|
||||
public IncompleteHandshakeException() {
|
||||
this.preferredSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter preferredSize
|
||||
*
|
||||
* @return the preferredSize
|
||||
*/
|
||||
public int getPreferredSize() {
|
||||
return preferredSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
/**
|
||||
* exception which indicates that a invalid data was received
|
||||
*/
|
||||
public class InvalidDataException extends Exception {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 3731842424390998726L;
|
||||
|
||||
/**
|
||||
* attribute which closecode will be returned
|
||||
*/
|
||||
private final int closecode;
|
||||
|
||||
/**
|
||||
* constructor for a InvalidDataException
|
||||
*
|
||||
* @param closecode the closecode which will be returned
|
||||
*/
|
||||
public InvalidDataException(int closecode) {
|
||||
this.closecode = closecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidDataException.
|
||||
*
|
||||
* @param closecode the closecode which will be returned.
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public InvalidDataException(int closecode, String s) {
|
||||
super(s);
|
||||
this.closecode = closecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidDataException.
|
||||
*
|
||||
* @param closecode the closecode which will be returned.
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidDataException(int closecode, Throwable t) {
|
||||
super(t);
|
||||
this.closecode = closecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidDataException.
|
||||
*
|
||||
* @param closecode the closecode which will be returned.
|
||||
* @param s the detail message.
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidDataException(int closecode, String s, Throwable t) {
|
||||
super(s, t);
|
||||
this.closecode = closecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter closecode
|
||||
*
|
||||
* @return the closecode
|
||||
*/
|
||||
public int getCloseCode() {
|
||||
return closecode;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package org.java_websocket.exceptions;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* The Character Encoding is not supported.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class InvalidEncodingException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* attribute for the encoding exception
|
||||
*/
|
||||
private final UnsupportedEncodingException encodingException;
|
||||
|
||||
/**
|
||||
* constructor for InvalidEncodingException
|
||||
*
|
||||
* @param encodingException the cause for this exception
|
||||
*/
|
||||
public InvalidEncodingException(UnsupportedEncodingException encodingException) {
|
||||
if (encodingException == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.encodingException = encodingException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception which includes more information on the unsupported encoding
|
||||
*
|
||||
* @return an UnsupportedEncodingException
|
||||
*/
|
||||
public UnsupportedEncodingException getEncodingException() {
|
||||
return encodingException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
/**
|
||||
* exception which indicates that a invalid frame was received (CloseFrame.PROTOCOL_ERROR)
|
||||
*/
|
||||
public class InvalidFrameException extends InvalidDataException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = -9016496369828887591L;
|
||||
|
||||
/**
|
||||
* constructor for a InvalidFrameException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*/
|
||||
public InvalidFrameException() {
|
||||
super(CloseFrame.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidFrameException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public InvalidFrameException(String s) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidFrameException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidFrameException(Throwable t) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidFrameException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param s the detail message.
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidFrameException(String s, Throwable t) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, s, t);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
/**
|
||||
* exception which indicates that a invalid handshake was received (CloseFrame.PROTOCOL_ERROR)
|
||||
*/
|
||||
public class InvalidHandshakeException extends InvalidDataException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = -1426533877490484964L;
|
||||
|
||||
/**
|
||||
* constructor for a InvalidHandshakeException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*/
|
||||
public InvalidHandshakeException() {
|
||||
super(CloseFrame.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidHandshakeException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param s the detail message.
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidHandshakeException(String s, Throwable t) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, s, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidHandshakeException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public InvalidHandshakeException(String s) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a InvalidHandshakeException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode PROTOCOL_ERROR
|
||||
*
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public InvalidHandshakeException(Throwable t) {
|
||||
super(CloseFrame.PROTOCOL_ERROR, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
/**
|
||||
* exception which indicates that the message limited was exceeded (CloseFrame.TOOBIG)
|
||||
*/
|
||||
public class LimitExceededException extends InvalidDataException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = 6908339749836826785L;
|
||||
|
||||
/**
|
||||
* A closer indication about the limit
|
||||
*/
|
||||
private final int limit;
|
||||
|
||||
/**
|
||||
* constructor for a LimitExceededException
|
||||
* <p>
|
||||
* calling LimitExceededException with closecode TOOBIG
|
||||
*/
|
||||
public LimitExceededException() {
|
||||
this(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a LimitExceededException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode TOOBIG
|
||||
* @param limit the allowed size which was not enough
|
||||
*/
|
||||
public LimitExceededException(int limit) {
|
||||
super(CloseFrame.TOOBIG);
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a LimitExceededException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode TOOBIG
|
||||
* @param s the detail message.
|
||||
* @param limit the allowed size which was not enough
|
||||
*/
|
||||
public LimitExceededException(String s, int limit) {
|
||||
super(CloseFrame.TOOBIG, s);
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a LimitExceededException
|
||||
* <p>
|
||||
* calling InvalidDataException with closecode TOOBIG
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public LimitExceededException(String s) {
|
||||
this(s, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the limit which was hit so this exception was caused
|
||||
*
|
||||
* @return the limit as int
|
||||
*/
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
/**
|
||||
* exception which indicates the frame payload is not sendable
|
||||
*/
|
||||
public class NotSendableException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = -6468967874576651628L;
|
||||
|
||||
/**
|
||||
* constructor for a NotSendableException
|
||||
*
|
||||
* @param s the detail message.
|
||||
*/
|
||||
public NotSendableException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a NotSendableException
|
||||
*
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public NotSendableException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor for a NotSendableException
|
||||
*
|
||||
* @param s the detail message.
|
||||
* @param t the throwable causing this exception.
|
||||
*/
|
||||
public NotSendableException(String s, Throwable t) {
|
||||
super(s, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
/**
|
||||
* exception which indicates the websocket is not yet connected (ReadyState.OPEN)
|
||||
*/
|
||||
public class WebsocketNotConnectedException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Serializable
|
||||
*/
|
||||
private static final long serialVersionUID = -785314021592982715L;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.java_websocket.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.java_websocket.WebSocket;
|
||||
|
||||
/**
|
||||
* Exception to wrap an IOException and include information about the websocket which had the
|
||||
* exception
|
||||
*
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public class WrappedIOException extends Exception {
|
||||
|
||||
/**
|
||||
* The websocket where the IOException happened
|
||||
*/
|
||||
private final transient WebSocket connection;
|
||||
|
||||
/**
|
||||
* The IOException
|
||||
*/
|
||||
private final IOException ioException;
|
||||
|
||||
/**
|
||||
* Wrapp an IOException and include the websocket
|
||||
*
|
||||
* @param connection the websocket where the IOException happened
|
||||
* @param ioException the IOException
|
||||
*/
|
||||
public WrappedIOException(WebSocket connection, IOException ioException) {
|
||||
this.connection = connection;
|
||||
this.ioException = ioException;
|
||||
}
|
||||
|
||||
/**
|
||||
* The websocket where the IOException happened
|
||||
*
|
||||
* @return the websocket for the wrapped IOException
|
||||
*/
|
||||
public WebSocket getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapped IOException
|
||||
*
|
||||
* @return IOException which is wrapped
|
||||
*/
|
||||
public IOException getIOException() {
|
||||
return ioException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all implementations in relation with the exceptions thrown in this
|
||||
* lib.
|
||||
*/
|
||||
package org.java_websocket.exceptions;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.extensions;
|
||||
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidFrameException;
|
||||
import org.java_websocket.framing.ControlFrame;
|
||||
import org.java_websocket.framing.DataFrame;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
|
||||
/**
|
||||
* Implementation for a compression extension specified by https://tools.ietf.org/html/rfc7692
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public abstract class CompressionExtension extends DefaultExtension {
|
||||
|
||||
@Override
|
||||
public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
|
||||
if ((inputFrame instanceof DataFrame) && (inputFrame.isRSV2() || inputFrame.isRSV3())) {
|
||||
throw new InvalidFrameException(
|
||||
"bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
|
||||
+ inputFrame.isRSV3());
|
||||
}
|
||||
if ((inputFrame instanceof ControlFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2()
|
||||
|| inputFrame.isRSV3())) {
|
||||
throw new InvalidFrameException(
|
||||
"bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
|
||||
+ inputFrame.isRSV3());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.extensions;
|
||||
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidFrameException;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
|
||||
/**
|
||||
* Class which represents the normal websocket implementation specified by rfc6455.
|
||||
* <p>
|
||||
* This is a fallback and will always be available for a Draft_6455
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public class DefaultExtension implements IExtension {
|
||||
|
||||
@Override
|
||||
public void decodeFrame(Framedata inputFrame) throws InvalidDataException {
|
||||
//Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encodeFrame(Framedata inputFrame) {
|
||||
//Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptProvidedExtensionAsServer(String inputExtension) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptProvidedExtensionAsClient(String inputExtension) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
|
||||
if (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()) {
|
||||
throw new InvalidFrameException(
|
||||
"bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
|
||||
+ inputFrame.isRSV3());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvidedExtensionAsClient() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvidedExtensionAsServer() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExtension copyInstance() {
|
||||
return new DefaultExtension();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
//Nothing to do here. No internal stats.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o != null && getClass() == o.getClass();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package org.java_websocket.extensions;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExtensionRequestData {
|
||||
|
||||
public static final String EMPTY_VALUE = "";
|
||||
|
||||
private Map<String, String> extensionParameters;
|
||||
private String extensionName;
|
||||
|
||||
private ExtensionRequestData() {
|
||||
extensionParameters = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static ExtensionRequestData parseExtensionRequest(String extensionRequest) {
|
||||
ExtensionRequestData extensionData = new ExtensionRequestData();
|
||||
String[] parts = extensionRequest.split(";");
|
||||
extensionData.extensionName = parts[0].trim();
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
String[] keyValue = parts[i].split("=");
|
||||
String value = EMPTY_VALUE;
|
||||
|
||||
// Some parameters don't take a value. For those that do, parse the value.
|
||||
if (keyValue.length > 1) {
|
||||
String tempValue = keyValue[1].trim();
|
||||
|
||||
// If the value is wrapped in quotes, just get the data between them.
|
||||
if ((tempValue.startsWith("\"") && tempValue.endsWith("\""))
|
||||
|| (tempValue.startsWith("'") && tempValue.endsWith("'"))
|
||||
&& tempValue.length() > 2) {
|
||||
tempValue = tempValue.substring(1, tempValue.length() - 1);
|
||||
}
|
||||
|
||||
value = tempValue;
|
||||
}
|
||||
|
||||
extensionData.extensionParameters.put(keyValue[0].trim(), value);
|
||||
}
|
||||
|
||||
return extensionData;
|
||||
}
|
||||
|
||||
public String getExtensionName() {
|
||||
return extensionName;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtensionParameters() {
|
||||
return extensionParameters;
|
||||
}
|
||||
}
|
||||
138
src/main/java/org/java_websocket/extensions/IExtension.java
Normal file
138
src/main/java/org/java_websocket/extensions/IExtension.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.extensions;
|
||||
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
|
||||
/**
|
||||
* Interface which specifies all required methods to develop a websocket extension.
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public interface IExtension {
|
||||
|
||||
/**
|
||||
* Decode a frame with a extension specific algorithm. The algorithm is subject to be implemented
|
||||
* by the specific extension. The resulting frame will be used in the application
|
||||
*
|
||||
* @param inputFrame the frame, which has do be decoded to be used in the application
|
||||
* @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly
|
||||
* implemented by the other endpoint or there are other protocol
|
||||
* errors/decoding errors
|
||||
* @since 1.3.5
|
||||
*/
|
||||
void decodeFrame(Framedata inputFrame) throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Encode a frame with a extension specific algorithm. The algorithm is subject to be implemented
|
||||
* by the specific extension. The resulting frame will be send to the other endpoint.
|
||||
*
|
||||
* @param inputFrame the frame, which has do be encoded to be used on the other endpoint
|
||||
* @since 1.3.5
|
||||
*/
|
||||
void encodeFrame(Framedata inputFrame);
|
||||
|
||||
/**
|
||||
* Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific
|
||||
* extension if the endpoint is in the role of a server
|
||||
*
|
||||
* @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the
|
||||
* other endpoint
|
||||
* @return true, if the offer does fit to this specific extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
boolean acceptProvidedExtensionAsServer(String inputExtensionHeader);
|
||||
|
||||
/**
|
||||
* Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific
|
||||
* extension if the endpoint is in the role of a client
|
||||
*
|
||||
* @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the
|
||||
* other endpoint
|
||||
* @return true, if the offer does fit to this specific extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
boolean acceptProvidedExtensionAsClient(String inputExtensionHeader);
|
||||
|
||||
/**
|
||||
* Check if the received frame is correctly implemented by the other endpoint and there are no
|
||||
* specification errors (like wrongly set RSV)
|
||||
*
|
||||
* @param inputFrame the received frame
|
||||
* @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly
|
||||
* implementing the specification for the specific extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
void isFrameValid(Framedata inputFrame) throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is
|
||||
* in the role of a client. If the extension returns an empty string (""), the offer will not be
|
||||
* included in the handshake.
|
||||
*
|
||||
* @return the specific Sec-WebSocket-Extensions header for this extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
String getProvidedExtensionAsClient();
|
||||
|
||||
/**
|
||||
* Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is
|
||||
* in the role of a server. If the extension returns an empty string (""), the offer will not be
|
||||
* included in the handshake.
|
||||
*
|
||||
* @return the specific Sec-WebSocket-Extensions header for this extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
String getProvidedExtensionAsServer();
|
||||
|
||||
/**
|
||||
* Extensions must only be by one websocket at all. To prevent extensions to be used more than
|
||||
* once the Websocket implementation should call this method in order to create a new usable
|
||||
* version of a given extension instance.<br> The copy can be safely used in conjunction with a
|
||||
* new websocket connection.
|
||||
*
|
||||
* @return a copy of the extension
|
||||
* @since 1.3.5
|
||||
*/
|
||||
IExtension copyInstance();
|
||||
|
||||
/**
|
||||
* Cleaning up internal stats when the draft gets reset.
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Return a string which should contain the class name as well as additional information about the
|
||||
* current configurations for this extension (DEBUG purposes)
|
||||
*
|
||||
* @return a string containing the class name as well as additional information
|
||||
* @since 1.3.5
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all interfaces and implementations in relation with the WebSocket
|
||||
* Sec-WebSocket-Extensions.
|
||||
*/
|
||||
package org.java_websocket.extensions;
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
package org.java_websocket.extensions.permessage_deflate;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidFrameException;
|
||||
import org.java_websocket.extensions.CompressionExtension;
|
||||
import org.java_websocket.extensions.ExtensionRequestData;
|
||||
import org.java_websocket.extensions.IExtension;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.framing.ContinuousFrame;
|
||||
import org.java_websocket.framing.DataFrame;
|
||||
import org.java_websocket.framing.Framedata;
|
||||
import org.java_websocket.framing.FramedataImpl1;
|
||||
|
||||
/**
|
||||
* PerMessage Deflate Extension (<a href="https://tools.ietf.org/html/rfc7692#section-7">7. The
|
||||
* "permessage-deflate" Extension</a> in
|
||||
* <a href="https://tools.ietf.org/html/rfc7692">RFC 7692</a>).
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7">7. The "permessage-deflate"
|
||||
* Extension in RFC 7692</a>
|
||||
*/
|
||||
public class PerMessageDeflateExtension extends CompressionExtension {
|
||||
|
||||
// Name of the extension as registered by IETF https://tools.ietf.org/html/rfc7692#section-9.
|
||||
private static final String EXTENSION_REGISTERED_NAME = "permessage-deflate";
|
||||
// Below values are defined for convenience. They are not used in the compression/decompression phase.
|
||||
// They may be needed during the extension-negotiation offer in the future.
|
||||
private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover";
|
||||
private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover";
|
||||
private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits";
|
||||
private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits";
|
||||
private static final int serverMaxWindowBits = 1 << 15;
|
||||
private static final int clientMaxWindowBits = 1 << 15;
|
||||
private static final byte[] TAIL_BYTES = {(byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF};
|
||||
private static final int BUFFER_SIZE = 1 << 10;
|
||||
|
||||
private int threshold = 1024;
|
||||
|
||||
private boolean serverNoContextTakeover = true;
|
||||
private boolean clientNoContextTakeover = false;
|
||||
|
||||
// For WebSocketServers, this variable holds the extension parameters that the peer client has requested.
|
||||
// For WebSocketClients, this variable holds the extension parameters that client himself has requested.
|
||||
private Map<String, String> requestedParameters = new LinkedHashMap<>();
|
||||
|
||||
private final int compressionLevel;
|
||||
|
||||
private final Inflater inflater;
|
||||
private final Deflater deflater;
|
||||
|
||||
/**
|
||||
* Constructor for the PerMessage Deflate Extension (<a href="https://tools.ietf.org/html/rfc7692#section-7">7. Thepermessage-deflate" Extension</a>)
|
||||
*
|
||||
* Uses {@link Deflater#DEFAULT_COMPRESSION} as the compression level for the {@link Deflater#Deflater(int)}
|
||||
*/
|
||||
public PerMessageDeflateExtension() {
|
||||
this(Deflater.DEFAULT_COMPRESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the PerMessage Deflate Extension (<a href="https://tools.ietf.org/html/rfc7692#section-7">7. Thepermessage-deflate" Extension</a>)
|
||||
*
|
||||
* @param compressionLevel The compression level passed to the {@link Deflater#Deflater(int)}
|
||||
*/
|
||||
public PerMessageDeflateExtension(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.deflater = new Deflater(this.compressionLevel, true);
|
||||
this.inflater = new Inflater(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compression level used for the compressor.
|
||||
* @return the compression level
|
||||
*/
|
||||
public int getCompressionLevel() {
|
||||
return this.compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size threshold for doing the compression
|
||||
* @return Size (in bytes) below which messages will not be compressed
|
||||
* @since 1.5.3
|
||||
*/
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size when payloads smaller than this will not be compressed.
|
||||
* @param threshold the size in bytes
|
||||
* @since 1.5.3
|
||||
*/
|
||||
public void setThreshold(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the "server_no_context_takeover" extension parameter
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7.1.1.1">The "server_no_context_takeover" Extension Parameter</a>
|
||||
* @return serverNoContextTakeover is the server no context parameter active
|
||||
*/
|
||||
public boolean isServerNoContextTakeover() {
|
||||
return serverNoContextTakeover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the "server_no_context_takeover" extension parameter
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7.1.1.1">The "server_no_context_takeover" Extension Parameter</a>
|
||||
* @param serverNoContextTakeover set the server no context parameter
|
||||
*/
|
||||
public void setServerNoContextTakeover(boolean serverNoContextTakeover) {
|
||||
this.serverNoContextTakeover = serverNoContextTakeover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the "client_no_context_takeover" extension parameter
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7.1.1.2">The "client_no_context_takeover" Extension Parameter</a>
|
||||
* @return clientNoContextTakeover is the client no context parameter active
|
||||
*/
|
||||
public boolean isClientNoContextTakeover() {
|
||||
return clientNoContextTakeover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the "client_no_context_takeover" extension parameter
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7.1.1.2">The "client_no_context_takeover" Extension Parameter</a>
|
||||
* @param clientNoContextTakeover set the client no context parameter
|
||||
*/
|
||||
public void setClientNoContextTakeover(boolean clientNoContextTakeover) {
|
||||
this.clientNoContextTakeover = clientNoContextTakeover;
|
||||
}
|
||||
|
||||
/*
|
||||
An endpoint uses the following algorithm to decompress a message.
|
||||
1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||
payload of the message.
|
||||
2. Decompress the resulting data using DEFLATE.
|
||||
See, https://tools.ietf.org/html/rfc7692#section-7.2.2
|
||||
*/
|
||||
@Override
|
||||
public void decodeFrame(Framedata inputFrame) throws InvalidDataException {
|
||||
// Only DataFrames can be decompressed.
|
||||
if (!(inputFrame instanceof DataFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inputFrame.isRSV1() && inputFrame.getOpcode() != Opcode.CONTINUOUS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// RSV1 bit must be set only for the first frame.
|
||||
if (inputFrame.getOpcode() == Opcode.CONTINUOUS && inputFrame.isRSV1()) {
|
||||
throw new InvalidDataException(CloseFrame.POLICY_VALIDATION,
|
||||
"RSV1 bit can only be set for the first frame.");
|
||||
}
|
||||
|
||||
// Decompressed output buffer.
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try {
|
||||
decompress(inputFrame.getPayloadData().array(), output);
|
||||
|
||||
/*
|
||||
If a message is "first fragmented and then compressed", as this project does, then the inflater
|
||||
can not inflate fragments except the first one.
|
||||
This behavior occurs most likely because those fragments end with "final deflate blocks".
|
||||
We can check the getRemaining() method to see whether the data we supplied has been decompressed or not.
|
||||
And if not, we just reset the inflater and decompress again.
|
||||
Note that this behavior doesn't occur if the message is "first compressed and then fragmented".
|
||||
*/
|
||||
if (inflater.getRemaining() > 0) {
|
||||
inflater.reset();
|
||||
decompress(inputFrame.getPayloadData().array(), output);
|
||||
}
|
||||
|
||||
if (inputFrame.isFin()) {
|
||||
decompress(TAIL_BYTES, output);
|
||||
// If context takeover is disabled, inflater can be reset.
|
||||
if (clientNoContextTakeover) {
|
||||
inflater.reset();
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, e.getMessage());
|
||||
}
|
||||
|
||||
// Set frames payload to the new decompressed data.
|
||||
((FramedataImpl1) inputFrame)
|
||||
.setPayload(ByteBuffer.wrap(output.toByteArray(), 0, output.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data the bytes of data
|
||||
* @param outputBuffer the output stream
|
||||
* @throws DataFormatException
|
||||
*/
|
||||
private void decompress(byte[] data, ByteArrayOutputStream outputBuffer)
|
||||
throws DataFormatException {
|
||||
inflater.setInput(data);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int bytesInflated;
|
||||
while ((bytesInflated = inflater.inflate(buffer)) > 0) {
|
||||
outputBuffer.write(buffer, 0, bytesInflated);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encodeFrame(Framedata inputFrame) {
|
||||
// Only DataFrames can be decompressed.
|
||||
if (!(inputFrame instanceof DataFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] payloadData = inputFrame.getPayloadData().array();
|
||||
if (payloadData.length < threshold) {
|
||||
return;
|
||||
}
|
||||
// Only the first frame's RSV1 must be set.
|
||||
if (!(inputFrame instanceof ContinuousFrame)) {
|
||||
((DataFrame) inputFrame).setRSV1(true);
|
||||
}
|
||||
|
||||
deflater.setInput(payloadData);
|
||||
// Compressed output buffer.
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
// Temporary buffer to hold compressed output.
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesCompressed;
|
||||
while ((bytesCompressed = deflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH))
|
||||
> 0) {
|
||||
output.write(buffer, 0, bytesCompressed);
|
||||
}
|
||||
|
||||
byte[] outputBytes = output.toByteArray();
|
||||
int outputLength = outputBytes.length;
|
||||
|
||||
/*
|
||||
https://tools.ietf.org/html/rfc7692#section-7.2.1 states that if the final fragment's compressed
|
||||
payload ends with 0x00 0x00 0xff 0xff, they should be removed.
|
||||
To simulate removal, we just pass 4 bytes less to the new payload
|
||||
if the frame is final and outputBytes ends with 0x00 0x00 0xff 0xff.
|
||||
*/
|
||||
if (inputFrame.isFin()) {
|
||||
if (endsWithTail(outputBytes)) {
|
||||
outputLength -= TAIL_BYTES.length;
|
||||
}
|
||||
|
||||
if (serverNoContextTakeover) {
|
||||
deflater.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Set frames payload to the new compressed data.
|
||||
((FramedataImpl1) inputFrame).setPayload(ByteBuffer.wrap(outputBytes, 0, outputLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data the bytes of data
|
||||
* @return true if the data is OK
|
||||
*/
|
||||
private static boolean endsWithTail(byte[] data) {
|
||||
if (data.length < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = data.length;
|
||||
for (int i = 0; i < TAIL_BYTES.length; i++) {
|
||||
if (TAIL_BYTES[i] != data[length - TAIL_BYTES.length + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptProvidedExtensionAsServer(String inputExtension) {
|
||||
String[] requestedExtensions = inputExtension.split(",");
|
||||
for (String extension : requestedExtensions) {
|
||||
ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
|
||||
if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Holds parameters that peer client has sent.
|
||||
Map<String, String> headers = extensionData.getExtensionParameters();
|
||||
requestedParameters.putAll(headers);
|
||||
if (requestedParameters.containsKey(CLIENT_NO_CONTEXT_TAKEOVER)) {
|
||||
clientNoContextTakeover = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptProvidedExtensionAsClient(String inputExtension) {
|
||||
String[] requestedExtensions = inputExtension.split(",");
|
||||
for (String extension : requestedExtensions) {
|
||||
ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
|
||||
if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Holds parameters that are sent by the server, as a response to our initial extension request.
|
||||
Map<String, String> headers = extensionData.getExtensionParameters();
|
||||
// After this point, parameters that the server sent back can be configured, but we don't use them for now.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvidedExtensionAsClient() {
|
||||
requestedParameters.put(CLIENT_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE);
|
||||
requestedParameters.put(SERVER_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE);
|
||||
|
||||
return EXTENSION_REGISTERED_NAME + "; " + SERVER_NO_CONTEXT_TAKEOVER + "; "
|
||||
+ CLIENT_NO_CONTEXT_TAKEOVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvidedExtensionAsServer() {
|
||||
return EXTENSION_REGISTERED_NAME
|
||||
+ "; " + SERVER_NO_CONTEXT_TAKEOVER
|
||||
+ (clientNoContextTakeover ? "; " + CLIENT_NO_CONTEXT_TAKEOVER : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExtension copyInstance() {
|
||||
PerMessageDeflateExtension clone = new PerMessageDeflateExtension(this.getCompressionLevel());
|
||||
clone.setThreshold(this.getThreshold());
|
||||
clone.setClientNoContextTakeover(this.isClientNoContextTakeover());
|
||||
clone.setServerNoContextTakeover(this.isServerNoContextTakeover());
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* This extension requires the RSV1 bit to be set only for the first frame. If the frame is type
|
||||
* is CONTINUOUS, RSV1 bit must be unset.
|
||||
*/
|
||||
@Override
|
||||
public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
|
||||
if ((inputFrame instanceof ContinuousFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2()
|
||||
|| inputFrame.isRSV3())) {
|
||||
throw new InvalidFrameException(
|
||||
"bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
|
||||
+ inputFrame.isRSV3());
|
||||
}
|
||||
super.isFrameValid(inputFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PerMessageDeflateExtension";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
41
src/main/java/org/java_websocket/framing/BinaryFrame.java
Normal file
41
src/main/java/org/java_websocket/framing/BinaryFrame.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
|
||||
/**
|
||||
* Class to represent a binary frame
|
||||
*/
|
||||
public class BinaryFrame extends DataFrame {
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to binary
|
||||
*/
|
||||
public BinaryFrame() {
|
||||
super(Opcode.BINARY);
|
||||
}
|
||||
}
|
||||
341
src/main/java/org/java_websocket/framing/CloseFrame.java
Normal file
341
src/main/java/org/java_websocket/framing/CloseFrame.java
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidFrameException;
|
||||
import org.java_websocket.util.ByteBufferUtils;
|
||||
import org.java_websocket.util.Charsetfunctions;
|
||||
|
||||
/**
|
||||
* Class to represent a close frame
|
||||
*/
|
||||
public class CloseFrame extends ControlFrame {
|
||||
|
||||
/**
|
||||
* indicates a normal closure, meaning whatever purpose the connection was established for has
|
||||
* been fulfilled.
|
||||
*/
|
||||
public static final int NORMAL = 1000;
|
||||
/**
|
||||
* 1001 indicates that an endpoint is "going away", such as a server going down, or a browser
|
||||
* having navigated away from a page.
|
||||
*/
|
||||
public static final int GOING_AWAY = 1001;
|
||||
/**
|
||||
* 1002 indicates that an endpoint is terminating the connection due to a protocol error.
|
||||
*/
|
||||
public static final int PROTOCOL_ERROR = 1002;
|
||||
/**
|
||||
* 1003 indicates that an endpoint is terminating the connection because it has received a type of
|
||||
* data it cannot accept (e.g. an endpoint that understands only text data MAY send this if it
|
||||
* receives a binary message).
|
||||
*/
|
||||
public static final int REFUSE = 1003;
|
||||
/*1004: Reserved. The specific meaning might be defined in the future.*/
|
||||
/**
|
||||
* 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
|
||||
* endpoint. It is designated for use in applications expecting a status code to indicate that no
|
||||
* status code was actually present.
|
||||
*/
|
||||
public static final int NOCODE = 1005;
|
||||
/**
|
||||
* 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
|
||||
* endpoint. It is designated for use in applications expecting a status code to indicate that the
|
||||
* connection was closed abnormally, e.g. without sending or receiving a Close control frame.
|
||||
*/
|
||||
public static final int ABNORMAL_CLOSE = 1006;
|
||||
/**
|
||||
* 1007 indicates that an endpoint is terminating the connection because it has received data
|
||||
* within a message that was not consistent with the type of the message (e.g., non-UTF-8
|
||||
* [RFC3629] data within a text message).
|
||||
*/
|
||||
public static final int NO_UTF8 = 1007;
|
||||
/**
|
||||
* 1008 indicates that an endpoint is terminating the connection because it has received a message
|
||||
* that violates its policy. This is a generic status code that can be returned when there is no
|
||||
* other more suitable status code (e.g. 1003 or 1009), or if there is a need to hide specific
|
||||
* details about the policy.
|
||||
*/
|
||||
public static final int POLICY_VALIDATION = 1008;
|
||||
/**
|
||||
* 1009 indicates that an endpoint is terminating the connection because it has received a message
|
||||
* which is too big for it to process.
|
||||
*/
|
||||
public static final int TOOBIG = 1009;
|
||||
/**
|
||||
* 1010 indicates that an endpoint (client) is terminating the connection because it has expected
|
||||
* the server to negotiate one or more extension, but the server didn't return them in the
|
||||
* response message of the WebSocket handshake. The list of extensions which are needed SHOULD
|
||||
* appear in the /reason/ part of the Close frame. Note that this status code is not used by the
|
||||
* server, because it can fail the WebSocket handshake instead.
|
||||
*/
|
||||
public static final int EXTENSION = 1010;
|
||||
/**
|
||||
* 1011 indicates that a server is terminating the connection because it encountered an unexpected
|
||||
* condition that prevented it from fulfilling the request.
|
||||
**/
|
||||
public static final int UNEXPECTED_CONDITION = 1011;
|
||||
/**
|
||||
* 1012 indicates that the service is restarted. A client may reconnect, and if it choses to do,
|
||||
* should reconnect using a randomized delay of 5 - 30s. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html
|
||||
* for more information.
|
||||
*
|
||||
* @since 1.3.8
|
||||
**/
|
||||
public static final int SERVICE_RESTART = 1012;
|
||||
/**
|
||||
* 1013 indicates that the service is experiencing overload. A client should only connect to a
|
||||
* different IP (when there are multiple for the target) or reconnect to the same IP upon user
|
||||
* action. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html for more
|
||||
* information.
|
||||
*
|
||||
* @since 1.3.8
|
||||
**/
|
||||
public static final int TRY_AGAIN_LATER = 1013;
|
||||
/**
|
||||
* 1014 indicates that the server was acting as a gateway or proxy and received an invalid
|
||||
* response from the upstream server. This is similar to 502 HTTP Status Code See
|
||||
* https://www.ietf.org/mail-archive/web/hybi/current/msg10748.html fore more information.
|
||||
*
|
||||
* @since 1.3.8
|
||||
**/
|
||||
public static final int BAD_GATEWAY = 1014;
|
||||
/**
|
||||
* 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
|
||||
* endpoint. It is designated for use in applications expecting a status code to indicate that the
|
||||
* connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate
|
||||
* can't be verified).
|
||||
**/
|
||||
public static final int TLS_ERROR = 1015;
|
||||
|
||||
/**
|
||||
* The connection had never been established
|
||||
*/
|
||||
public static final int NEVER_CONNECTED = -1;
|
||||
|
||||
/**
|
||||
* The connection had a buggy close (this should not happen)
|
||||
*/
|
||||
public static final int BUGGYCLOSE = -2;
|
||||
|
||||
/**
|
||||
* The connection was flushed and closed
|
||||
*/
|
||||
public static final int FLASHPOLICY = -3;
|
||||
|
||||
|
||||
/**
|
||||
* The close code used in this close frame
|
||||
*/
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* The close message used in this close frame
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* Constructor for a close frame
|
||||
* <p>
|
||||
* Using opcode closing and fin = true
|
||||
*/
|
||||
public CloseFrame() {
|
||||
super(Opcode.CLOSING);
|
||||
setReason("");
|
||||
setCode(CloseFrame.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the close code for this close frame
|
||||
*
|
||||
* @param code the close code
|
||||
*/
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
// CloseFrame.TLS_ERROR is not allowed to be transferred over the wire
|
||||
if (code == CloseFrame.TLS_ERROR) {
|
||||
this.code = CloseFrame.NOCODE;
|
||||
this.reason = "";
|
||||
}
|
||||
updatePayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the close reason for this close frame
|
||||
*
|
||||
* @param reason the reason code
|
||||
*/
|
||||
public void setReason(String reason) {
|
||||
if (reason == null) {
|
||||
reason = "";
|
||||
}
|
||||
this.reason = reason;
|
||||
updatePayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the used close code
|
||||
*
|
||||
* @return the used close code
|
||||
*/
|
||||
public int getCloseCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message that closeframe is containing
|
||||
*
|
||||
* @return the message in this frame
|
||||
*/
|
||||
public String getMessage() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "code: " + code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isValid() throws InvalidDataException {
|
||||
super.isValid();
|
||||
if (code == CloseFrame.NO_UTF8 && reason.isEmpty()) {
|
||||
throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
|
||||
}
|
||||
if (code == CloseFrame.NOCODE && 0 < reason.length()) {
|
||||
throw new InvalidDataException(PROTOCOL_ERROR,
|
||||
"A close frame must have a closecode if it has a reason");
|
||||
}
|
||||
//Intentional check for code != CloseFrame.TLS_ERROR just to make sure even if the code earlier changes
|
||||
if ((code > CloseFrame.TLS_ERROR && code < 3000)) {
|
||||
throw new InvalidDataException(PROTOCOL_ERROR, "Trying to send an illegal close code!");
|
||||
}
|
||||
if (code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR
|
||||
|| code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004) {
|
||||
throw new InvalidFrameException("closecode must not be sent over the wire: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPayload(ByteBuffer payload) {
|
||||
code = CloseFrame.NOCODE;
|
||||
reason = "";
|
||||
payload.mark();
|
||||
if (payload.remaining() == 0) {
|
||||
code = CloseFrame.NORMAL;
|
||||
} else if (payload.remaining() == 1) {
|
||||
code = CloseFrame.PROTOCOL_ERROR;
|
||||
} else {
|
||||
if (payload.remaining() >= 2) {
|
||||
ByteBuffer bb = ByteBuffer.allocate(4);
|
||||
bb.position(2);
|
||||
bb.putShort(payload.getShort());
|
||||
bb.position(0);
|
||||
code = bb.getInt();
|
||||
}
|
||||
payload.reset();
|
||||
try {
|
||||
int mark = payload.position();// because stringUtf8 also creates a mark
|
||||
validateUtf8(payload, mark);
|
||||
} catch (InvalidDataException e) {
|
||||
code = CloseFrame.NO_UTF8;
|
||||
reason = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the payload to valid utf8
|
||||
*
|
||||
* @param mark the current mark
|
||||
* @param payload the current payload
|
||||
* @throws InvalidDataException the current payload is not a valid utf8
|
||||
*/
|
||||
private void validateUtf8(ByteBuffer payload, int mark) throws InvalidDataException {
|
||||
try {
|
||||
payload.position(payload.position() + 2);
|
||||
reason = Charsetfunctions.stringUtf8(payload);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidDataException(CloseFrame.NO_UTF8);
|
||||
} finally {
|
||||
payload.position(mark);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the payload to represent the close code and the reason
|
||||
*/
|
||||
private void updatePayload() {
|
||||
byte[] by = Charsetfunctions.utf8Bytes(reason);
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
buf.putInt(code);
|
||||
buf.position(2);
|
||||
ByteBuffer pay = ByteBuffer.allocate(2 + by.length);
|
||||
pay.put(buf);
|
||||
pay.put(by);
|
||||
pay.rewind();
|
||||
super.setPayload(pay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getPayloadData() {
|
||||
if (code == NOCODE) {
|
||||
return ByteBufferUtils.getEmptyByteBuffer();
|
||||
}
|
||||
return super.getPayloadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseFrame that = (CloseFrame) o;
|
||||
|
||||
if (code != that.code) {
|
||||
return false;
|
||||
}
|
||||
return reason != null ? reason.equals(that.reason) : that.reason == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + code;
|
||||
result = 31 * result + (reason != null ? reason.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
|
||||
/**
|
||||
* Class to represent a continuous frame
|
||||
*/
|
||||
public class ContinuousFrame extends DataFrame {
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to continuous
|
||||
*/
|
||||
public ContinuousFrame() {
|
||||
super(Opcode.CONTINUOUS);
|
||||
}
|
||||
}
|
||||
61
src/main/java/org/java_websocket/framing/ControlFrame.java
Normal file
61
src/main/java/org/java_websocket/framing/ControlFrame.java
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.exceptions.InvalidFrameException;
|
||||
|
||||
/**
|
||||
* Abstract class to represent control frames
|
||||
*/
|
||||
public abstract class ControlFrame extends FramedataImpl1 {
|
||||
|
||||
/**
|
||||
* Class to represent a control frame
|
||||
*
|
||||
* @param opcode the opcode to use
|
||||
*/
|
||||
public ControlFrame(Opcode opcode) {
|
||||
super(opcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isValid() throws InvalidDataException {
|
||||
if (!isFin()) {
|
||||
throw new InvalidFrameException("Control frame can't have fin==false set");
|
||||
}
|
||||
if (isRSV1()) {
|
||||
throw new InvalidFrameException("Control frame can't have rsv1==true set");
|
||||
}
|
||||
if (isRSV2()) {
|
||||
throw new InvalidFrameException("Control frame can't have rsv2==true set");
|
||||
}
|
||||
if (isRSV3()) {
|
||||
throw new InvalidFrameException("Control frame can't have rsv3==true set");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/main/java/org/java_websocket/framing/DataFrame.java
Normal file
49
src/main/java/org/java_websocket/framing/DataFrame.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
|
||||
/**
|
||||
* Abstract class to represent data frames
|
||||
*/
|
||||
public abstract class DataFrame extends FramedataImpl1 {
|
||||
|
||||
/**
|
||||
* Class to represent a data frame
|
||||
*
|
||||
* @param opcode the opcode to use
|
||||
*/
|
||||
public DataFrame(Opcode opcode) {
|
||||
super(opcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isValid() throws InvalidDataException {
|
||||
//Nothing specific to check
|
||||
}
|
||||
}
|
||||
94
src/main/java/org/java_websocket/framing/Framedata.java
Normal file
94
src/main/java/org/java_websocket/framing/Framedata.java
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
|
||||
/**
|
||||
* The interface for the frame
|
||||
*/
|
||||
public interface Framedata {
|
||||
|
||||
/**
|
||||
* Indicates that this is the final fragment in a message. The first fragment MAY also be the
|
||||
* final fragment.
|
||||
*
|
||||
* @return true, if this frame is the final fragment
|
||||
*/
|
||||
boolean isFin();
|
||||
|
||||
/**
|
||||
* Indicates that this frame has the rsv1 bit set.
|
||||
*
|
||||
* @return true, if this frame has the rsv1 bit set
|
||||
*/
|
||||
boolean isRSV1();
|
||||
|
||||
/**
|
||||
* Indicates that this frame has the rsv2 bit set.
|
||||
*
|
||||
* @return true, if this frame has the rsv2 bit set
|
||||
*/
|
||||
boolean isRSV2();
|
||||
|
||||
/**
|
||||
* Indicates that this frame has the rsv3 bit set.
|
||||
*
|
||||
* @return true, if this frame has the rsv3 bit set
|
||||
*/
|
||||
boolean isRSV3();
|
||||
|
||||
/**
|
||||
* Defines whether the "Payload data" is masked.
|
||||
*
|
||||
* @return true, "Payload data" is masked
|
||||
*/
|
||||
boolean getTransfereMasked();
|
||||
|
||||
/**
|
||||
* Defines the interpretation of the "Payload data".
|
||||
*
|
||||
* @return the interpretation as a Opcode
|
||||
*/
|
||||
Opcode getOpcode();
|
||||
|
||||
/**
|
||||
* The "Payload data" which was sent in this frame
|
||||
*
|
||||
* @return the "Payload data" as ByteBuffer
|
||||
*/
|
||||
ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done
|
||||
|
||||
/**
|
||||
* Appends an additional frame to the current frame
|
||||
* <p>
|
||||
* This methods does not override the opcode, but does override the fin
|
||||
*
|
||||
* @param nextframe the additional frame
|
||||
*/
|
||||
void append(Framedata nextframe);
|
||||
}
|
||||
294
src/main/java/org/java_websocket/framing/FramedataImpl1.java
Normal file
294
src/main/java/org/java_websocket/framing/FramedataImpl1.java
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.util.ByteBufferUtils;
|
||||
|
||||
/**
|
||||
* Abstract implementation of a frame
|
||||
*/
|
||||
public abstract class FramedataImpl1 implements Framedata {
|
||||
|
||||
/**
|
||||
* Indicates that this is the final fragment in a message.
|
||||
*/
|
||||
private boolean fin;
|
||||
/**
|
||||
* Defines the interpretation of the "Payload data".
|
||||
*/
|
||||
private Opcode optcode;
|
||||
|
||||
/**
|
||||
* The unmasked "Payload data" which was sent in this frame
|
||||
*/
|
||||
private ByteBuffer unmaskedpayload;
|
||||
|
||||
/**
|
||||
* Defines whether the "Payload data" is masked.
|
||||
*/
|
||||
private boolean transferemasked;
|
||||
|
||||
/**
|
||||
* Indicates that the rsv1 bit is set or not
|
||||
*/
|
||||
private boolean rsv1;
|
||||
|
||||
/**
|
||||
* Indicates that the rsv2 bit is set or not
|
||||
*/
|
||||
private boolean rsv2;
|
||||
|
||||
/**
|
||||
* Indicates that the rsv3 bit is set or not
|
||||
*/
|
||||
private boolean rsv3;
|
||||
|
||||
/**
|
||||
* Check if the frame is valid due to specification
|
||||
*
|
||||
* @throws InvalidDataException thrown if the frame is not a valid frame
|
||||
*/
|
||||
public abstract void isValid() throws InvalidDataException;
|
||||
|
||||
/**
|
||||
* Constructor for a FramedataImpl without any attributes set apart from the opcode
|
||||
*
|
||||
* @param op the opcode to use
|
||||
*/
|
||||
public FramedataImpl1(Opcode op) {
|
||||
optcode = op;
|
||||
unmaskedpayload = ByteBufferUtils.getEmptyByteBuffer();
|
||||
fin = true;
|
||||
transferemasked = false;
|
||||
rsv1 = false;
|
||||
rsv2 = false;
|
||||
rsv3 = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRSV1() {
|
||||
return rsv1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRSV2() {
|
||||
return rsv2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRSV3() {
|
||||
return rsv3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFin() {
|
||||
return fin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Opcode getOpcode() {
|
||||
return optcode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTransfereMasked() {
|
||||
return transferemasked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getPayloadData() {
|
||||
return unmaskedpayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(Framedata nextframe) {
|
||||
ByteBuffer b = nextframe.getPayloadData();
|
||||
if (unmaskedpayload == null) {
|
||||
unmaskedpayload = ByteBuffer.allocate(b.remaining());
|
||||
b.mark();
|
||||
unmaskedpayload.put(b);
|
||||
b.reset();
|
||||
} else {
|
||||
b.mark();
|
||||
unmaskedpayload.position(unmaskedpayload.limit());
|
||||
unmaskedpayload.limit(unmaskedpayload.capacity());
|
||||
|
||||
if (b.remaining() > unmaskedpayload.remaining()) {
|
||||
ByteBuffer tmp = ByteBuffer.allocate(b.remaining() + unmaskedpayload.capacity());
|
||||
unmaskedpayload.flip();
|
||||
tmp.put(unmaskedpayload);
|
||||
tmp.put(b);
|
||||
unmaskedpayload = tmp;
|
||||
|
||||
} else {
|
||||
unmaskedpayload.put(b);
|
||||
}
|
||||
unmaskedpayload.rewind();
|
||||
b.reset();
|
||||
}
|
||||
fin = nextframe.isFin();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Framedata{ opcode:" + getOpcode() + ", fin:" + isFin() + ", rsv1:" + isRSV1()
|
||||
+ ", rsv2:" + isRSV2() + ", rsv3:" + isRSV3() + ", payload length:[pos:" + unmaskedpayload
|
||||
.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + (
|
||||
unmaskedpayload.remaining() > 1000 ? "(too big to display)"
|
||||
: new String(unmaskedpayload.array())) + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the payload of this frame to the provided payload
|
||||
*
|
||||
* @param payload the payload which is to set
|
||||
*/
|
||||
public void setPayload(ByteBuffer payload) {
|
||||
this.unmaskedpayload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fin of this frame to the provided boolean
|
||||
*
|
||||
* @param fin true if fin has to be set
|
||||
*/
|
||||
public void setFin(boolean fin) {
|
||||
this.fin = fin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rsv1 of this frame to the provided boolean
|
||||
*
|
||||
* @param rsv1 true if rsv1 has to be set
|
||||
*/
|
||||
public void setRSV1(boolean rsv1) {
|
||||
this.rsv1 = rsv1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rsv2 of this frame to the provided boolean
|
||||
*
|
||||
* @param rsv2 true if rsv2 has to be set
|
||||
*/
|
||||
public void setRSV2(boolean rsv2) {
|
||||
this.rsv2 = rsv2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rsv3 of this frame to the provided boolean
|
||||
*
|
||||
* @param rsv3 true if rsv3 has to be set
|
||||
*/
|
||||
public void setRSV3(boolean rsv3) {
|
||||
this.rsv3 = rsv3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tranferemask of this frame to the provided boolean
|
||||
*
|
||||
* @param transferemasked true if transferemasked has to be set
|
||||
*/
|
||||
public void setTransferemasked(boolean transferemasked) {
|
||||
this.transferemasked = transferemasked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a frame with a specific opcode
|
||||
*
|
||||
* @param opcode the opcode representing the frame
|
||||
* @return the frame with a specific opcode
|
||||
*/
|
||||
public static FramedataImpl1 get(Opcode opcode) {
|
||||
if (opcode == null) {
|
||||
throw new IllegalArgumentException("Supplied opcode cannot be null");
|
||||
}
|
||||
switch (opcode) {
|
||||
case PING:
|
||||
return new PingFrame();
|
||||
case PONG:
|
||||
return new PongFrame();
|
||||
case TEXT:
|
||||
return new TextFrame();
|
||||
case BINARY:
|
||||
return new BinaryFrame();
|
||||
case CLOSING:
|
||||
return new CloseFrame();
|
||||
case CONTINUOUS:
|
||||
return new ContinuousFrame();
|
||||
default:
|
||||
throw new IllegalArgumentException("Supplied opcode is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FramedataImpl1 that = (FramedataImpl1) o;
|
||||
|
||||
if (fin != that.fin) {
|
||||
return false;
|
||||
}
|
||||
if (transferemasked != that.transferemasked) {
|
||||
return false;
|
||||
}
|
||||
if (rsv1 != that.rsv1) {
|
||||
return false;
|
||||
}
|
||||
if (rsv2 != that.rsv2) {
|
||||
return false;
|
||||
}
|
||||
if (rsv3 != that.rsv3) {
|
||||
return false;
|
||||
}
|
||||
if (optcode != that.optcode) {
|
||||
return false;
|
||||
}
|
||||
return unmaskedpayload != null ? unmaskedpayload.equals(that.unmaskedpayload)
|
||||
: that.unmaskedpayload == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (fin ? 1 : 0);
|
||||
result = 31 * result + optcode.hashCode();
|
||||
result = 31 * result + (unmaskedpayload != null ? unmaskedpayload.hashCode() : 0);
|
||||
result = 31 * result + (transferemasked ? 1 : 0);
|
||||
result = 31 * result + (rsv1 ? 1 : 0);
|
||||
result = 31 * result + (rsv2 ? 1 : 0);
|
||||
result = 31 * result + (rsv3 ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
41
src/main/java/org/java_websocket/framing/PingFrame.java
Normal file
41
src/main/java/org/java_websocket/framing/PingFrame.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
|
||||
/**
|
||||
* Class to represent a ping frame
|
||||
*/
|
||||
public class PingFrame extends ControlFrame {
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to ping
|
||||
*/
|
||||
public PingFrame() {
|
||||
super(Opcode.PING);
|
||||
}
|
||||
}
|
||||
51
src/main/java/org/java_websocket/framing/PongFrame.java
Normal file
51
src/main/java/org/java_websocket/framing/PongFrame.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
|
||||
/**
|
||||
* Class to represent a pong frame
|
||||
*/
|
||||
public class PongFrame extends ControlFrame {
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to pong
|
||||
*/
|
||||
public PongFrame() {
|
||||
super(Opcode.PONG);
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to ping copying over the payload of the ping
|
||||
*
|
||||
* @param pingFrame the PingFrame which payload is to copy
|
||||
*/
|
||||
public PongFrame(PingFrame pingFrame) {
|
||||
super(Opcode.PONG);
|
||||
setPayload(pingFrame.getPayloadData());
|
||||
}
|
||||
}
|
||||
51
src/main/java/org/java_websocket/framing/TextFrame.java
Normal file
51
src/main/java/org/java_websocket/framing/TextFrame.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.framing;
|
||||
|
||||
import org.java_websocket.enums.Opcode;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.util.Charsetfunctions;
|
||||
|
||||
/**
|
||||
* Class to represent a text frames
|
||||
*/
|
||||
public class TextFrame extends DataFrame {
|
||||
|
||||
/**
|
||||
* constructor which sets the opcode of this frame to text
|
||||
*/
|
||||
public TextFrame() {
|
||||
super(Opcode.TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isValid() throws InvalidDataException {
|
||||
super.isValid();
|
||||
if (!Charsetfunctions.isValidUTF8(getPayloadData())) {
|
||||
throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/main/java/org/java_websocket/framing/package-info.java
Normal file
30
src/main/java/org/java_websocket/framing/package-info.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all interfaces and implementations in relation with the WebSocket
|
||||
* frames.
|
||||
*/
|
||||
package org.java_websocket.framing;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* The interface for a client handshake
|
||||
*/
|
||||
public interface ClientHandshake extends Handshakedata {
|
||||
|
||||
/**
|
||||
* returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
|
||||
*
|
||||
* @return the HTTP Request-URI
|
||||
*/
|
||||
String getResourceDescriptor();
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* The interface for building a handshake for the client
|
||||
*/
|
||||
public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake {
|
||||
|
||||
/**
|
||||
* Set a specific resource descriptor
|
||||
*
|
||||
* @param resourceDescriptor the resource descriptior to set
|
||||
*/
|
||||
void setResourceDescriptor(String resourceDescriptor);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* The interface for building a handshake
|
||||
*/
|
||||
public interface HandshakeBuilder extends Handshakedata {
|
||||
|
||||
/**
|
||||
* Setter for the content of the handshake
|
||||
*
|
||||
* @param content the content to set
|
||||
*/
|
||||
void setContent(byte[] content);
|
||||
|
||||
/**
|
||||
* Adding a specific field with a specific value
|
||||
*
|
||||
* @param name the http field
|
||||
* @param value the value for this field
|
||||
*/
|
||||
void put(String name, String value);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* Implementation for a client handshake
|
||||
*/
|
||||
public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder {
|
||||
|
||||
/**
|
||||
* Attribute for the resource descriptor
|
||||
*/
|
||||
private String resourceDescriptor = "*";
|
||||
|
||||
@Override
|
||||
public void setResourceDescriptor(String resourceDescriptor) {
|
||||
if (resourceDescriptor == null) {
|
||||
throw new IllegalArgumentException("http resource descriptor must not be null");
|
||||
}
|
||||
this.resourceDescriptor = resourceDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceDescriptor() {
|
||||
return resourceDescriptor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* Implementation for a server handshake
|
||||
*/
|
||||
public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder {
|
||||
|
||||
/**
|
||||
* Attribute for the http status
|
||||
*/
|
||||
private short httpstatus;
|
||||
|
||||
/**
|
||||
* Attribute for the http status message
|
||||
*/
|
||||
private String httpstatusmessage;
|
||||
|
||||
@Override
|
||||
public String getHttpStatusMessage() {
|
||||
return httpstatusmessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getHttpStatus() {
|
||||
return httpstatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHttpStatusMessage(String message) {
|
||||
this.httpstatusmessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHttpStatus(short status) {
|
||||
httpstatus = status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* The interface for the data of a handshake
|
||||
*/
|
||||
public interface Handshakedata {
|
||||
|
||||
/**
|
||||
* Iterator for the http fields
|
||||
*
|
||||
* @return the http fields
|
||||
*/
|
||||
Iterator<String> iterateHttpFields();
|
||||
|
||||
/**
|
||||
* Gets the value of the field
|
||||
*
|
||||
* @param name The name of the field
|
||||
* @return the value of the field or an empty String if not in the handshake
|
||||
*/
|
||||
String getFieldValue(String name);
|
||||
|
||||
/**
|
||||
* Checks if this handshake contains a specific field
|
||||
*
|
||||
* @param name The name of the field
|
||||
* @return true, if it contains the field
|
||||
*/
|
||||
boolean hasFieldValue(String name);
|
||||
|
||||
/**
|
||||
* Get the content of the handshake
|
||||
*
|
||||
* @return the content as byte-array
|
||||
*/
|
||||
byte[] getContent();
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Implementation of a handshake builder
|
||||
*/
|
||||
public class HandshakedataImpl1 implements HandshakeBuilder {
|
||||
|
||||
/**
|
||||
* Attribute for the content of the handshake
|
||||
*/
|
||||
private byte[] content;
|
||||
|
||||
/**
|
||||
* Attribute for the http fields and values
|
||||
*/
|
||||
private TreeMap<String, String> map;
|
||||
|
||||
/**
|
||||
* Constructor for handshake implementation
|
||||
*/
|
||||
public HandshakedataImpl1() {
|
||||
map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterateHttpFields() {
|
||||
return Collections.unmodifiableSet(map.keySet()).iterator();// Safety first
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldValue(String name) {
|
||||
String s = map.get(name);
|
||||
if (s == null) {
|
||||
return "";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(byte[] content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, String value) {
|
||||
map.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFieldValue(String name) {
|
||||
return map.containsKey(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* Interface for the server handshake
|
||||
*/
|
||||
public interface ServerHandshake extends Handshakedata {
|
||||
|
||||
/**
|
||||
* Get the http status code
|
||||
*
|
||||
* @return the http status code
|
||||
*/
|
||||
short getHttpStatus();
|
||||
|
||||
/**
|
||||
* Get the http status message
|
||||
*
|
||||
* @return the http status message
|
||||
*/
|
||||
String getHttpStatusMessage();
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
/**
|
||||
* The interface for building a handshake for the server
|
||||
*/
|
||||
public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake {
|
||||
|
||||
/**
|
||||
* Setter for the http status code
|
||||
*
|
||||
* @param status the http status code
|
||||
*/
|
||||
void setHttpStatus(short status);
|
||||
|
||||
/**
|
||||
* Setter for the http status message
|
||||
*
|
||||
* @param message the http status message
|
||||
*/
|
||||
void setHttpStatusMessage(String message);
|
||||
}
|
||||
31
src/main/java/org/java_websocket/handshake/package-info.java
Normal file
31
src/main/java/org/java_websocket/handshake/package-info.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all interfaces and implementations in relation with the WebSocket
|
||||
* handshake.
|
||||
*/
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
44
src/main/java/org/java_websocket/interfaces/ISSLChannel.java
Normal file
44
src/main/java/org/java_websocket/interfaces/ISSLChannel.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.java_websocket.interfaces;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
* Interface which specifies all required methods a SSLSocketChannel has to make public.
|
||||
*
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public interface ISSLChannel {
|
||||
|
||||
/**
|
||||
* Get the ssl engine used for the de- and encryption of the communication.
|
||||
*
|
||||
* @return the ssl engine of this channel
|
||||
*/
|
||||
SSLEngine getSSLEngine();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all new interfaces.
|
||||
*/
|
||||
package org.java_websocket.interfaces;
|
||||
72
src/main/java/org/java_websocket/protocols/IProtocol.java
Normal file
72
src/main/java/org/java_websocket/protocols/IProtocol.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.protocols;
|
||||
|
||||
/**
|
||||
* Interface which specifies all required methods for a Sec-WebSocket-Protocol
|
||||
*
|
||||
* @since 1.3.7
|
||||
*/
|
||||
public interface IProtocol {
|
||||
|
||||
/**
|
||||
* Check if the received Sec-WebSocket-Protocol header field contains a offer for the specific
|
||||
* protocol
|
||||
*
|
||||
* @param inputProtocolHeader the received Sec-WebSocket-Protocol header field offered by the
|
||||
* other endpoint
|
||||
* @return true, if the offer does fit to this specific protocol
|
||||
* @since 1.3.7
|
||||
*/
|
||||
boolean acceptProvidedProtocol(String inputProtocolHeader);
|
||||
|
||||
/**
|
||||
* Return the specific Sec-WebSocket-protocol header offer for this protocol if the endpoint. If
|
||||
* the extension returns an empty string (""), the offer will not be included in the handshake.
|
||||
*
|
||||
* @return the specific Sec-WebSocket-Protocol header for this protocol
|
||||
* @since 1.3.7
|
||||
*/
|
||||
String getProvidedProtocol();
|
||||
|
||||
/**
|
||||
* To prevent protocols to be used more than once the Websocket implementation should call this
|
||||
* method in order to create a new usable version of a given protocol instance.
|
||||
*
|
||||
* @return a copy of the protocol
|
||||
* @since 1.3.7
|
||||
*/
|
||||
IProtocol copyInstance();
|
||||
|
||||
/**
|
||||
* Return a string which should contain the protocol name as well as additional information about
|
||||
* the current configurations for this protocol (DEBUG purposes)
|
||||
*
|
||||
* @return a string containing the protocol name as well as additional information
|
||||
* @since 1.3.7
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
105
src/main/java/org/java_websocket/protocols/Protocol.java
Normal file
105
src/main/java/org/java_websocket/protocols/Protocol.java
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.protocols;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Class which represents the protocol used as Sec-WebSocket-Protocol
|
||||
*
|
||||
* @since 1.3.7
|
||||
*/
|
||||
public class Protocol implements IProtocol {
|
||||
|
||||
private static final Pattern patternSpace = Pattern.compile(" ");
|
||||
private static final Pattern patternComma = Pattern.compile(",");
|
||||
|
||||
/**
|
||||
* Attribute for the provided protocol
|
||||
*/
|
||||
private final String providedProtocol;
|
||||
|
||||
/**
|
||||
* Constructor for a Sec-Websocket-Protocol
|
||||
*
|
||||
* @param providedProtocol the protocol string
|
||||
*/
|
||||
public Protocol(String providedProtocol) {
|
||||
if (providedProtocol == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.providedProtocol = providedProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptProvidedProtocol(String inputProtocolHeader) {
|
||||
if ("".equals(providedProtocol)) {
|
||||
return true;
|
||||
}
|
||||
String protocolHeader = patternSpace.matcher(inputProtocolHeader).replaceAll("");
|
||||
String[] headers = patternComma.split(protocolHeader);
|
||||
for (String header : headers) {
|
||||
if (providedProtocol.equals(header)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvidedProtocol() {
|
||||
return this.providedProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IProtocol copyInstance() {
|
||||
return new Protocol(getProvidedProtocol());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getProvidedProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Protocol protocol = (Protocol) o;
|
||||
|
||||
return providedProtocol.equals(protocol.providedProtocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return providedProtocol.hashCode();
|
||||
}
|
||||
}
|
||||
30
src/main/java/org/java_websocket/protocols/package-info.java
Normal file
30
src/main/java/org/java_websocket/protocols/package-info.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all interfaces and implementations in relation with the WebSocket
|
||||
* Sec-WebSocket-Protocol.
|
||||
*/
|
||||
package org.java_websocket.protocols;
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import org.java_websocket.SSLSocketChannel2;
|
||||
|
||||
/**
|
||||
* WebSocketFactory that can be configured to only support specific protocols and cipher suites.
|
||||
*/
|
||||
public class CustomSSLWebSocketServerFactory extends DefaultSSLWebSocketServerFactory {
|
||||
|
||||
/**
|
||||
* The enabled protocols saved as a String array
|
||||
*/
|
||||
private final String[] enabledProtocols;
|
||||
|
||||
/**
|
||||
* The enabled ciphersuites saved as a String array
|
||||
*/
|
||||
private final String[] enabledCiphersuites;
|
||||
|
||||
/**
|
||||
* New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
|
||||
* suites.
|
||||
*
|
||||
* @param sslContext - can not be <code>null</code>
|
||||
* @param enabledProtocols - only these protocols are enabled, when <code>null</code> default
|
||||
* settings will be used.
|
||||
* @param enabledCiphersuites - only these cipher suites are enabled, when <code>null</code>
|
||||
* default settings will be used.
|
||||
*/
|
||||
public CustomSSLWebSocketServerFactory(SSLContext sslContext, String[] enabledProtocols,
|
||||
String[] enabledCiphersuites) {
|
||||
this(sslContext, Executors.newSingleThreadScheduledExecutor(), enabledProtocols,
|
||||
enabledCiphersuites);
|
||||
}
|
||||
|
||||
/**
|
||||
* New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
|
||||
* suites.
|
||||
*
|
||||
* @param sslContext - can not be <code>null</code>
|
||||
* @param executerService - can not be <code>null</code>
|
||||
* @param enabledProtocols - only these protocols are enabled, when <code>null</code> default
|
||||
* settings will be used.
|
||||
* @param enabledCiphersuites - only these cipher suites are enabled, when <code>null</code>
|
||||
* default settings will be used.
|
||||
*/
|
||||
public CustomSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService,
|
||||
String[] enabledProtocols, String[] enabledCiphersuites) {
|
||||
super(sslContext, executerService);
|
||||
this.enabledProtocols = enabledProtocols;
|
||||
this.enabledCiphersuites = enabledCiphersuites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
|
||||
SSLEngine e = sslcontext.createSSLEngine();
|
||||
if (enabledProtocols != null) {
|
||||
e.setEnabledProtocols(enabledProtocols);
|
||||
}
|
||||
if (enabledCiphersuites != null) {
|
||||
e.setEnabledCipherSuites(enabledCiphersuites);
|
||||
}
|
||||
e.setUseClientMode(false);
|
||||
return new SSLSocketChannel2(channel, e, exec, key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import org.java_websocket.SSLSocketChannel2;
|
||||
import org.java_websocket.WebSocketAdapter;
|
||||
import org.java_websocket.WebSocketImpl;
|
||||
import org.java_websocket.WebSocketServerFactory;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
|
||||
public class DefaultSSLWebSocketServerFactory implements WebSocketServerFactory {
|
||||
|
||||
protected SSLContext sslcontext;
|
||||
protected ExecutorService exec;
|
||||
|
||||
public DefaultSSLWebSocketServerFactory(SSLContext sslContext) {
|
||||
this(sslContext, Executors.newSingleThreadScheduledExecutor());
|
||||
}
|
||||
|
||||
public DefaultSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService exec) {
|
||||
if (sslContext == null || exec == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.sslcontext = sslContext;
|
||||
this.exec = exec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
|
||||
SSLEngine e = sslcontext.createSSLEngine();
|
||||
/*
|
||||
* See https://github.com/TooTallNate/Java-WebSocket/issues/466
|
||||
*
|
||||
* We remove TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 from the enabled ciphers since it is just available when you patch your java installation directly.
|
||||
* E.g. firefox requests this cipher and this causes some dcs/instable connections
|
||||
*/
|
||||
List<String> ciphers = new ArrayList<>(Arrays.asList(e.getEnabledCipherSuites()));
|
||||
ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
|
||||
e.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()]));
|
||||
e.setUseClientMode(false);
|
||||
return new SSLSocketChannel2(channel, e, exec, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) {
|
||||
return new WebSocketImpl(a, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketImpl createWebSocket(WebSocketAdapter a, List<Draft> d) {
|
||||
return new WebSocketImpl(a, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
exec.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.server;
|
||||
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.List;
|
||||
import org.java_websocket.WebSocketAdapter;
|
||||
import org.java_websocket.WebSocketImpl;
|
||||
import org.java_websocket.WebSocketServerFactory;
|
||||
import org.java_websocket.drafts.Draft;
|
||||
|
||||
public class DefaultWebSocketServerFactory implements WebSocketServerFactory {
|
||||
|
||||
@Override
|
||||
public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) {
|
||||
return new WebSocketImpl(a, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketImpl createWebSocket(WebSocketAdapter a, List<Draft> d) {
|
||||
return new WebSocketImpl(a, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketChannel wrapChannel(SocketChannel channel, SelectionKey key) {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
//Nothing to do for a normal ws factory
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import org.java_websocket.SSLSocketChannel2;
|
||||
|
||||
/**
|
||||
* WebSocketFactory that can be configured to only support specific protocols and cipher suites.
|
||||
*/
|
||||
public class SSLParametersWebSocketServerFactory extends DefaultSSLWebSocketServerFactory {
|
||||
|
||||
private final SSLParameters sslParameters;
|
||||
|
||||
/**
|
||||
* New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
|
||||
* suites.
|
||||
*
|
||||
* @param sslContext - can not be <code>null</code>
|
||||
* @param sslParameters - can not be <code>null</code>
|
||||
*/
|
||||
public SSLParametersWebSocketServerFactory(SSLContext sslContext, SSLParameters sslParameters) {
|
||||
this(sslContext, Executors.newSingleThreadScheduledExecutor(), sslParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
|
||||
* suites.
|
||||
*
|
||||
* @param sslContext - can not be <code>null</code>
|
||||
* @param executerService - can not be <code>null</code>
|
||||
* @param sslParameters - can not be <code>null</code>
|
||||
*/
|
||||
public SSLParametersWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService,
|
||||
SSLParameters sslParameters) {
|
||||
super(sslContext, executerService);
|
||||
if (sslParameters == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.sslParameters = sslParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
|
||||
SSLEngine e = sslcontext.createSSLEngine();
|
||||
e.setUseClientMode(false);
|
||||
e.setSSLParameters(sslParameters);
|
||||
return new SSLSocketChannel2(channel, e, exec, key);
|
||||
}
|
||||
}
|
||||
1171
src/main/java/org/java_websocket/server/WebSocketServer.java
Normal file
1171
src/main/java/org/java_websocket/server/WebSocketServer.java
Normal file
File diff suppressed because it is too large
Load Diff
29
src/main/java/org/java_websocket/server/package-info.java
Normal file
29
src/main/java/org/java_websocket/server/package-info.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates all implementations in relation with the WebSocketServer.
|
||||
*/
|
||||
package org.java_websocket.server;
|
||||
1049
src/main/java/org/java_websocket/util/Base64.java
Normal file
1049
src/main/java/org/java_websocket/util/Base64.java
Normal file
File diff suppressed because it is too large
Load Diff
73
src/main/java/org/java_websocket/util/ByteBufferUtils.java
Normal file
73
src/main/java/org/java_websocket/util/ByteBufferUtils.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Utility class for ByteBuffers
|
||||
*/
|
||||
public class ByteBufferUtils {
|
||||
|
||||
/**
|
||||
* Private constructor for static class
|
||||
*/
|
||||
private ByteBufferUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer from one ByteBuffer to another ByteBuffer
|
||||
*
|
||||
* @param source the ByteBuffer to copy from
|
||||
* @param dest the ByteBuffer to copy to
|
||||
* @return the number of transferred bytes
|
||||
*/
|
||||
public static int transferByteBuffer(ByteBuffer source, ByteBuffer dest) {
|
||||
if (source == null || dest == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
int fremain = source.remaining();
|
||||
int toremain = dest.remaining();
|
||||
if (fremain > toremain) {
|
||||
int limit = Math.min(fremain, toremain);
|
||||
source.limit(limit);
|
||||
dest.put(source);
|
||||
return limit;
|
||||
} else {
|
||||
dest.put(source);
|
||||
return fremain;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ByteBuffer with zero capacity
|
||||
*
|
||||
* @return empty ByteBuffer
|
||||
*/
|
||||
public static ByteBuffer getEmptyByteBuffer() {
|
||||
return ByteBuffer.allocate(0);
|
||||
}
|
||||
}
|
||||
154
src/main/java/org/java_websocket/util/Charsetfunctions.java
Normal file
154
src/main/java/org/java_websocket/util/Charsetfunctions.java
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.java_websocket.exceptions.InvalidDataException;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
|
||||
public class Charsetfunctions {
|
||||
|
||||
/**
|
||||
* Private constructor for real static class
|
||||
*/
|
||||
private Charsetfunctions() {
|
||||
}
|
||||
|
||||
private static final CodingErrorAction codingErrorAction = CodingErrorAction.REPORT;
|
||||
|
||||
/*
|
||||
* @return UTF-8 encoding in bytes
|
||||
*/
|
||||
public static byte[] utf8Bytes(String s) {
|
||||
return s.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/*
|
||||
* @return ASCII encoding in bytes
|
||||
*/
|
||||
public static byte[] asciiBytes(String s) {
|
||||
return s.getBytes(StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public static String stringAscii(byte[] bytes) {
|
||||
return stringAscii(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String stringAscii(byte[] bytes, int offset, int length) {
|
||||
return new String(bytes, offset, length, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public static String stringUtf8(byte[] bytes) throws InvalidDataException {
|
||||
return stringUtf8(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
public static String stringUtf8(ByteBuffer bytes) throws InvalidDataException {
|
||||
CharsetDecoder decode = StandardCharsets.UTF_8.newDecoder();
|
||||
decode.onMalformedInput(codingErrorAction);
|
||||
decode.onUnmappableCharacter(codingErrorAction);
|
||||
String s;
|
||||
try {
|
||||
bytes.mark();
|
||||
s = decode.decode(bytes).toString();
|
||||
bytes.reset();
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new InvalidDataException(CloseFrame.NO_UTF8, e);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the "Flexible and Economical UTF-8 Decoder" algorithm by Björn Höhrmann
|
||||
* (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/)
|
||||
*/
|
||||
private static final int[] utf8d = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, // 00..1f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, // 20..3f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, // 40..5f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, // 60..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, // 80..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, // a0..bf
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, // c0..df
|
||||
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
|
||||
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
|
||||
1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1,
|
||||
1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
// s7..s8
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the provided BytebBuffer contains a valid utf8 encoded string.
|
||||
* <p>
|
||||
* Using the algorithm "Flexible and Economical UTF-8 Decoder" by Björn Höhrmann
|
||||
* (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/)
|
||||
*
|
||||
* @param data the ByteBuffer
|
||||
* @param off offset (for performance reasons)
|
||||
* @return does the ByteBuffer contain a valid utf8 encoded string
|
||||
*/
|
||||
public static boolean isValidUTF8(ByteBuffer data, int off) {
|
||||
int len = data.remaining();
|
||||
if (len < off) {
|
||||
return false;
|
||||
}
|
||||
int state = 0;
|
||||
for (int i = off; i < len; ++i) {
|
||||
state = utf8d[256 + (state << 4) + utf8d[(0xff & data.get(i))]];
|
||||
if (state == 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling isValidUTF8 with offset 0
|
||||
*
|
||||
* @param data the ByteBuffer
|
||||
* @return does the ByteBuffer contain a valid utf8 encoded string
|
||||
*/
|
||||
public static boolean isValidUTF8(ByteBuffer data) {
|
||||
return isValidUTF8(data, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.java_websocket.util;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class NamedThreadFactory implements ThreadFactory {
|
||||
|
||||
private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String threadPrefix;
|
||||
private final boolean daemon;
|
||||
|
||||
public NamedThreadFactory(String threadPrefix) {
|
||||
this.threadPrefix = threadPrefix;
|
||||
this.daemon = false;
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String threadPrefix, boolean daemon) {
|
||||
this.threadPrefix = threadPrefix;
|
||||
this.daemon = daemon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable runnable) {
|
||||
Thread thread = defaultThreadFactory.newThread(runnable);
|
||||
thread.setDaemon(daemon);
|
||||
thread.setName(threadPrefix + "-" + threadNumber);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
29
src/main/java/org/java_websocket/util/package-info.java
Normal file
29
src/main/java/org/java_websocket/util/package-info.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Nathan Rajlich
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package encapsulates the utility classes.
|
||||
*/
|
||||
package org.java_websocket.util;
|
||||
287
src/main/java/org/json/CDL.java
Normal file
287
src/main/java/org/json/CDL.java
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
The Software shall be used for Good, not Evil.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This provides static methods to convert comma delimited text into a
|
||||
* JSONArray, and to convert a JSONArray into comma delimited text. Comma
|
||||
* delimited text is a very popular format for data interchange. It is
|
||||
* understood by most database, spreadsheet, and organizer programs.
|
||||
* <p>
|
||||
* Each row of text represents a row in a table or a data record. Each row
|
||||
* ends with a NEWLINE character. Each row contains one or more values.
|
||||
* Values are separated by commas. A value can contain any character except
|
||||
* for comma, unless is is wrapped in single quotes or double quotes.
|
||||
* <p>
|
||||
* The first row usually contains the names of the columns.
|
||||
* <p>
|
||||
* A comma delimited list can be converted into a JSONArray of JSONObjects.
|
||||
* The names for the elements in the JSONObjects can be taken from the names
|
||||
* in the first row.
|
||||
* @author JSON.org
|
||||
* @version 2016-05-01
|
||||
*/
|
||||
public class CDL {
|
||||
|
||||
/**
|
||||
* Get the next value. The value can be wrapped in quotes. The value can
|
||||
* be empty.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return The value string, or null if empty.
|
||||
* @throws JSONException if the quoted string is badly formed.
|
||||
*/
|
||||
private static String getValue(JSONTokener x) throws JSONException {
|
||||
char c;
|
||||
char q;
|
||||
StringBuilder sb;
|
||||
do {
|
||||
c = x.next();
|
||||
} while (c == ' ' || c == '\t');
|
||||
switch (c) {
|
||||
case 0:
|
||||
return null;
|
||||
case '"':
|
||||
case '\'':
|
||||
q = c;
|
||||
sb = new StringBuilder();
|
||||
for (;;) {
|
||||
c = x.next();
|
||||
if (c == q) {
|
||||
//Handle escaped double-quote
|
||||
char nextC = x.next();
|
||||
if(nextC != '\"') {
|
||||
// if our quote was the end of the file, don't step
|
||||
if(nextC > 0) {
|
||||
x.back();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c == 0 || c == '\n' || c == '\r') {
|
||||
throw x.syntaxError("Missing close quote '" + q + "'.");
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
case ',':
|
||||
x.back();
|
||||
return "";
|
||||
default:
|
||||
x.back();
|
||||
return x.nextTo(',');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of strings from a row of comma delimited values.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONArray of strings.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
|
||||
JSONArray ja = new JSONArray();
|
||||
for (;;) {
|
||||
String value = getValue(x);
|
||||
char c = x.next();
|
||||
if (value == null ||
|
||||
(ja.length() == 0 && value.length() == 0 && c != ',')) {
|
||||
return null;
|
||||
}
|
||||
ja.put(value);
|
||||
for (;;) {
|
||||
if (c == ',') {
|
||||
break;
|
||||
}
|
||||
if (c != ' ') {
|
||||
if (c == '\n' || c == '\r' || c == 0) {
|
||||
return ja;
|
||||
}
|
||||
throw x.syntaxError("Bad character '" + c + "' (" +
|
||||
(int)c + ").");
|
||||
}
|
||||
c = x.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONObject from a row of comma delimited text, using a
|
||||
* parallel JSONArray of strings to provides the names of the elements.
|
||||
* @param names A JSONArray of names. This is commonly obtained from the
|
||||
* first row of a comma delimited text file using the rowToJSONArray
|
||||
* method.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONObject combining the names and values.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
|
||||
throws JSONException {
|
||||
JSONArray ja = rowToJSONArray(x);
|
||||
return ja != null ? ja.toJSONObject(names) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text row from a JSONArray. Values containing
|
||||
* the comma character will be quoted. Troublesome characters may be
|
||||
* removed.
|
||||
* @param ja A JSONArray of strings.
|
||||
* @return A string ending in NEWLINE.
|
||||
*/
|
||||
public static String rowToString(JSONArray ja) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < ja.length(); i += 1) {
|
||||
if (i > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
Object object = ja.opt(i);
|
||||
if (object != null) {
|
||||
String string = object.toString();
|
||||
if (string.length() > 0 && (string.indexOf(',') >= 0 ||
|
||||
string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 ||
|
||||
string.indexOf(0) >= 0 || string.charAt(0) == '"')) {
|
||||
sb.append('"');
|
||||
int length = string.length();
|
||||
for (int j = 0; j < length; j += 1) {
|
||||
char c = string.charAt(j);
|
||||
if (c >= ' ' && c != '"') {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
sb.append('"');
|
||||
} else {
|
||||
sb.append(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string,
|
||||
* using the first row as a source of names.
|
||||
* @param string The comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONArray toJSONArray(String string) throws JSONException {
|
||||
return toJSONArray(new JSONTokener(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string,
|
||||
* using the first row as a source of names.
|
||||
* @param x The JSONTokener containing the comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
|
||||
return toJSONArray(rowToJSONArray(x), x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string
|
||||
* using a supplied JSONArray as the source of element names.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param string The comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONArray names, String string)
|
||||
throws JSONException {
|
||||
return toJSONArray(names, new JSONTokener(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string
|
||||
* using a supplied JSONArray as the source of element names.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
|
||||
throws JSONException {
|
||||
if (names == null || names.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
JSONArray ja = new JSONArray();
|
||||
for (;;) {
|
||||
JSONObject jo = rowToJSONObject(names, x);
|
||||
if (jo == null) {
|
||||
break;
|
||||
}
|
||||
ja.put(jo);
|
||||
}
|
||||
if (ja.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text from a JSONArray of JSONObjects. The
|
||||
* first row will be a list of names obtained by inspecting the first
|
||||
* JSONObject.
|
||||
* @param ja A JSONArray of JSONObjects.
|
||||
* @return A comma delimited text.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static String toString(JSONArray ja) throws JSONException {
|
||||
JSONObject jo = ja.optJSONObject(0);
|
||||
if (jo != null) {
|
||||
JSONArray names = jo.names();
|
||||
if (names != null) {
|
||||
return rowToString(names) + toString(names, ja);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text from a JSONArray of JSONObjects using
|
||||
* a provided list of names. The list of names is not included in the
|
||||
* output.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param ja A JSONArray of JSONObjects.
|
||||
* @return A comma delimited text.
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static String toString(JSONArray names, JSONArray ja)
|
||||
throws JSONException {
|
||||
if (names == null || names.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < ja.length(); i += 1) {
|
||||
JSONObject jo = ja.optJSONObject(i);
|
||||
if (jo != null) {
|
||||
sb.append(rowToString(jo.toJSONArray(names)));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
224
src/main/java/org/json/Cookie.java
Normal file
224
src/main/java/org/json/Cookie.java
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package org.json;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
The Software shall be used for Good, not Evil.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a web browser cookie specification to a JSONObject and back.
|
||||
* JSON and Cookies are both notations for name/value pairs.
|
||||
* See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
|
||||
* @author JSON.org
|
||||
* @version 2015-12-09
|
||||
*/
|
||||
public class Cookie {
|
||||
|
||||
/**
|
||||
* Produce a copy of a string in which the characters '+', '%', '=', ';'
|
||||
* and control characters are replaced with "%hh". This is a gentle form
|
||||
* of URL encoding, attempting to cause as little distortion to the
|
||||
* string as possible. The characters '=' and ';' are meta characters in
|
||||
* cookies. By convention, they are escaped using the URL-encoding. This is
|
||||
* only a convention, not a standard. Often, cookies are expected to have
|
||||
* encoded values. We encode '=' and ';' because we must. We encode '%' and
|
||||
* '+' because they are meta characters in URL encoding.
|
||||
* @param string The source string.
|
||||
* @return The escaped result.
|
||||
*/
|
||||
public static String escape(String string) {
|
||||
char c;
|
||||
String s = string.trim();
|
||||
int length = s.length();
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i += 1) {
|
||||
c = s.charAt(i);
|
||||
if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
|
||||
sb.append('%');
|
||||
sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
|
||||
sb.append(Character.forDigit((char)(c & 0x0f), 16));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a cookie specification string into a JSONObject. The string
|
||||
* must contain a name value pair separated by '='. The name and the value
|
||||
* will be unescaped, possibly converting '+' and '%' sequences. The
|
||||
* cookie properties may follow, separated by ';', also represented as
|
||||
* name=value (except the Attribute properties like "Secure" or "HttpOnly",
|
||||
* which do not have a value. The value {@link Boolean#TRUE} will be used for these).
|
||||
* The name will be stored under the key "name", and the value will be
|
||||
* stored under the key "value". This method does not do checking or
|
||||
* validation of the parameters. It only converts the cookie string into
|
||||
* a JSONObject. All attribute names are converted to lower case keys in the
|
||||
* JSONObject (HttpOnly => httponly). If an attribute is specified more than
|
||||
* once, only the value found closer to the end of the cookie-string is kept.
|
||||
* @param string The cookie specification string.
|
||||
* @return A JSONObject containing "name", "value", and possibly other
|
||||
* members.
|
||||
* @throws JSONException If there is an error parsing the Cookie String.
|
||||
* Cookie strings must have at least one '=' character and the 'name'
|
||||
* portion of the cookie must not be blank.
|
||||
*/
|
||||
public static JSONObject toJSONObject(String string) {
|
||||
final JSONObject jo = new JSONObject();
|
||||
String name;
|
||||
Object value;
|
||||
|
||||
|
||||
JSONTokener x = new JSONTokener(string);
|
||||
|
||||
name = unescape(x.nextTo('=').trim());
|
||||
//per RFC6265, if the name is blank, the cookie should be ignored.
|
||||
if("".equals(name)) {
|
||||
throw new JSONException("Cookies must have a 'name'");
|
||||
}
|
||||
jo.put("name", name);
|
||||
// per RFC6265, if there is no '=', the cookie should be ignored.
|
||||
// the 'next' call here throws an exception if the '=' is not found.
|
||||
x.next('=');
|
||||
jo.put("value", unescape(x.nextTo(';')).trim());
|
||||
// discard the ';'
|
||||
x.next();
|
||||
// parse the remaining cookie attributes
|
||||
while (x.more()) {
|
||||
name = unescape(x.nextTo("=;")).trim().toLowerCase(Locale.ROOT);
|
||||
// don't allow a cookies attributes to overwrite it's name or value.
|
||||
if("name".equalsIgnoreCase(name)) {
|
||||
throw new JSONException("Illegal attribute name: 'name'");
|
||||
}
|
||||
if("value".equalsIgnoreCase(name)) {
|
||||
throw new JSONException("Illegal attribute name: 'value'");
|
||||
}
|
||||
// check to see if it's a flag property
|
||||
if (x.next() != '=') {
|
||||
value = Boolean.TRUE;
|
||||
} else {
|
||||
value = unescape(x.nextTo(';')).trim();
|
||||
x.next();
|
||||
}
|
||||
// only store non-blank attributes
|
||||
if(!"".equals(name) && !"".equals(value)) {
|
||||
jo.put(name, value);
|
||||
}
|
||||
}
|
||||
return jo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a JSONObject into a cookie specification string. The JSONObject
|
||||
* must contain "name" and "value" members (case insensitive).
|
||||
* If the JSONObject contains other members, they will be appended to the cookie
|
||||
* specification string. User-Agents are instructed to ignore unknown attributes,
|
||||
* so ensure your JSONObject is using only known attributes.
|
||||
* See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
|
||||
* @param jo A JSONObject
|
||||
* @return A cookie specification string
|
||||
* @throws JSONException thrown if the cookie has no name.
|
||||
*/
|
||||
public static String toString(JSONObject jo) throws JSONException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String name = null;
|
||||
Object value = null;
|
||||
for(String key : jo.keySet()){
|
||||
if("name".equalsIgnoreCase(key)) {
|
||||
name = jo.getString(key).trim();
|
||||
}
|
||||
if("value".equalsIgnoreCase(key)) {
|
||||
value=jo.getString(key).trim();
|
||||
}
|
||||
if(name != null && value != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(name == null || "".equals(name.trim())) {
|
||||
throw new JSONException("Cookie does not have a name");
|
||||
}
|
||||
if(value == null) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
sb.append(escape(name));
|
||||
sb.append("=");
|
||||
sb.append(escape((String)value));
|
||||
|
||||
for(String key : jo.keySet()){
|
||||
if("name".equalsIgnoreCase(key)
|
||||
|| "value".equalsIgnoreCase(key)) {
|
||||
// already processed above
|
||||
continue;
|
||||
}
|
||||
value = jo.opt(key);
|
||||
if(value instanceof Boolean) {
|
||||
if(Boolean.TRUE.equals(value)) {
|
||||
sb.append(';').append(escape(key));
|
||||
}
|
||||
// don't emit false values
|
||||
} else {
|
||||
sb.append(';')
|
||||
.append(escape(key))
|
||||
.append('=')
|
||||
.append(escape(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert <code>%</code><i>hh</i> sequences to single characters, and
|
||||
* convert plus to space.
|
||||
* @param string A string that may contain
|
||||
* <code>+</code> <small>(plus)</small> and
|
||||
* <code>%</code><i>hh</i> sequences.
|
||||
* @return The unescaped string.
|
||||
*/
|
||||
public static String unescape(String string) {
|
||||
int length = string.length();
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
char c = string.charAt(i);
|
||||
if (c == '+') {
|
||||
c = ' ';
|
||||
} else if (c == '%' && i + 2 < length) {
|
||||
int d = JSONTokener.dehexchar(string.charAt(i + 1));
|
||||
int e = JSONTokener.dehexchar(string.charAt(i + 2));
|
||||
if (d >= 0 && e >= 0) {
|
||||
c = (char)(d * 16 + e);
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
86
src/main/java/org/json/CookieList.java
Normal file
86
src/main/java/org/json/CookieList.java
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
The Software shall be used for Good, not Evil.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a web browser cookie list string to a JSONObject and back.
|
||||
* @author JSON.org
|
||||
* @version 2015-12-09
|
||||
*/
|
||||
public class CookieList {
|
||||
|
||||
/**
|
||||
* Convert a cookie list into a JSONObject. A cookie list is a sequence
|
||||
* of name/value pairs. The names are separated from the values by '='.
|
||||
* The pairs are separated by ';'. The names and the values
|
||||
* will be unescaped, possibly converting '+' and '%' sequences.
|
||||
*
|
||||
* To add a cookie to a cookie list,
|
||||
* cookielistJSONObject.put(cookieJSONObject.getString("name"),
|
||||
* cookieJSONObject.getString("value"));
|
||||
* @param string A cookie list string
|
||||
* @return A JSONObject
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static JSONObject toJSONObject(String string) throws JSONException {
|
||||
JSONObject jo = new JSONObject();
|
||||
JSONTokener x = new JSONTokener(string);
|
||||
while (x.more()) {
|
||||
String name = Cookie.unescape(x.nextTo('='));
|
||||
x.next('=');
|
||||
jo.put(name, Cookie.unescape(x.nextTo(';')));
|
||||
x.next();
|
||||
}
|
||||
return jo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JSONObject into a cookie list. A cookie list is a sequence
|
||||
* of name/value pairs. The names are separated from the values by '='.
|
||||
* The pairs are separated by ';'. The characters '%', '+', '=', and ';'
|
||||
* in the names and values are replaced by "%hh".
|
||||
* @param jo A JSONObject
|
||||
* @return A cookie list string
|
||||
* @throws JSONException if a called function fails
|
||||
*/
|
||||
public static String toString(JSONObject jo) throws JSONException {
|
||||
boolean b = false;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
// Don't use the new entrySet API to maintain Android support
|
||||
for (final String key : jo.keySet()) {
|
||||
final Object value = jo.opt(key);
|
||||
if (!JSONObject.NULL.equals(value)) {
|
||||
if (b) {
|
||||
sb.append(';');
|
||||
}
|
||||
sb.append(Cookie.escape(key));
|
||||
sb.append("=");
|
||||
sb.append(Cookie.escape(value.toString()));
|
||||
b = true;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user