This commit is contained in:
yaohunya 2025-07-18 22:50:07 +08:00
commit 6ae2ece6d6
171 changed files with 29862 additions and 0 deletions

BIN
lib/BungeeCord.jar Normal file

Binary file not shown.

28
pom.xml Normal file
View 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>

View 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;
}
}

View 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);
}
// 可以在这里加对 imageface 等其他类型的支持
}
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);
}
// 可以在这里加对 imageface 等其他类型的支持
}
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();
}
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}
}

View 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);
}
}

View 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]", "");
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View File

@ -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;
}
}

View 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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}
}

View 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();
}
}

View 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();
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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);
}

View 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();
}

View 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();
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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;

View 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();
}
}

File diff suppressed because it is too large Load Diff

View 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;

View File

@ -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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View 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 implementations in relation with the exceptions thrown in this
* lib.
*/
package org.java_websocket.exceptions;

View File

@ -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());
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View 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();
}

View 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-Extensions.
*/
package org.java_websocket.extensions;

View File

@ -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&#46; 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&#46; 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&#46; 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&#46; 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";
}
}

View 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);
}
}

View 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;
}
}

View 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 continuous frame
*/
public class ContinuousFrame extends DataFrame {
/**
* constructor which sets the opcode of this frame to continuous
*/
public ContinuousFrame() {
super(Opcode.CONTINUOUS);
}
}

View 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");
}
}
}

View 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
}
}

View 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);
}

View 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;
}
}

View 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);
}
}

View 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());
}
}

View 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!");
}
}
}

View 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;

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}

View 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;

View 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();
}

View 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 new interfaces.
*/
package org.java_websocket.interfaces;

View 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();
}

View 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();
}
}

View 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;

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View 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;

File diff suppressed because it is too large Load Diff

View 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);
}
}

View 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);
}
}

View File

@ -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;
}
}

View 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;

View 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();
}
}

View 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 =&gt; 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>&nbsp;<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();
}
}

View 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