Properly check Jids everywhere
This commit is contained in:
parent
3218dc0211
commit
a0a956ee08
|
@ -158,6 +158,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
set(GTK3_GLOBAL_VERSION 3.22)
|
set(GTK3_GLOBAL_VERSION 3.22)
|
||||||
set(GLib_GLOBAL_VERSION 2.38)
|
set(GLib_GLOBAL_VERSION 2.38)
|
||||||
|
set(ICU_GLOBAL_VERSION 57)
|
||||||
|
|
||||||
if (NOT VALA_EXECUTABLE)
|
if (NOT VALA_EXECUTABLE)
|
||||||
unset(VALA_EXECUTABLE CACHE)
|
unset(VALA_EXECUTABLE CACHE)
|
||||||
|
|
|
@ -78,6 +78,12 @@ public interface Application : GLib.Application {
|
||||||
while (jid[0] == '/') {
|
while (jid[0] == '/') {
|
||||||
jid = jid.substring(1);
|
jid = jid.substring(1);
|
||||||
}
|
}
|
||||||
|
jid = Uri.unescape_string(jid);
|
||||||
|
try {
|
||||||
|
jid = new Xmpp.Jid(jid).to_string();
|
||||||
|
} catch (Xmpp.InvalidJidError e) {
|
||||||
|
warning("Received invalid jid in xmpp:-URI: %s", e.message);
|
||||||
|
}
|
||||||
string query = "message";
|
string query = "message";
|
||||||
Gee.Map<string, string> options = new Gee.HashMap<string, string>();
|
Gee.Map<string, string> options = new Gee.HashMap<string, string>();
|
||||||
if (m.length == 2) {
|
if (m.length == 2) {
|
||||||
|
@ -85,7 +91,7 @@ public interface Application : GLib.Application {
|
||||||
query = cmds[0];
|
query = cmds[0];
|
||||||
for (int i = 1; i < cmds.length; ++i) {
|
for (int i = 1; i < cmds.length; ++i) {
|
||||||
string[] opt = cmds[i].split("=", 2);
|
string[] opt = cmds[i].split("=", 2);
|
||||||
options[opt[0]] = opt.length == 2 ? opt[1] : "";
|
options[Uri.unescape_string(opt[0])] = opt.length == 2 ? Uri.unescape_string(opt[1]) : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activate();
|
activate();
|
||||||
|
|
|
@ -6,10 +6,11 @@ namespace Dino.Entities {
|
||||||
public class Account : Object {
|
public class Account : Object {
|
||||||
|
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
public string localpart { get { return bare_jid.localpart; } }
|
public string localpart { get { return full_jid.localpart; } }
|
||||||
public string domainpart { get { return bare_jid.domainpart; } }
|
public string domainpart { get { return full_jid.domainpart; } }
|
||||||
public string resourcepart { get; set; }
|
public string resourcepart { get { return full_jid.resourcepart;} }
|
||||||
public Jid bare_jid { get; private set; }
|
public Jid bare_jid { owned get { return full_jid.bare_jid; } }
|
||||||
|
public Jid full_jid { get; private set; }
|
||||||
public string? password { get; set; }
|
public string? password { get; set; }
|
||||||
public string display_name {
|
public string display_name {
|
||||||
owned get { return alias ?? bare_jid.to_string(); }
|
owned get { return alias ?? bare_jid.to_string(); }
|
||||||
|
@ -23,17 +24,28 @@ public class Account : Object {
|
||||||
|
|
||||||
public Account(Jid bare_jid, string? resourcepart, string? password, string? alias) {
|
public Account(Jid bare_jid, string? resourcepart, string? password, string? alias) {
|
||||||
this.id = -1;
|
this.id = -1;
|
||||||
this.resourcepart = resourcepart ?? "dino." + Random.next_int().to_string("%x");
|
if (resourcepart != null) {
|
||||||
this.bare_jid = bare_jid;
|
try {
|
||||||
|
this.full_jid = bare_jid.with_resource(resourcepart);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Tried to create account with invalid resource (%s), defaulting to auto generated", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.full_jid == null) {
|
||||||
|
try {
|
||||||
|
this.full_jid = bare_jid.with_resource("dino." + Random.next_int().to_string("%x"));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
error("Auto-generated resource was invalid (%s)", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account.from_row(Database db, Qlite.Row row) {
|
public Account.from_row(Database db, Qlite.Row row) throws InvalidJidError {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
id = row[db.account.id];
|
id = row[db.account.id];
|
||||||
resourcepart = row[db.account.resourcepart];
|
full_jid = new Jid(row[db.account.bare_jid]).with_resource(row[db.account.resourcepart]);
|
||||||
bare_jid = new Jid(row[db.account.bare_jid]);
|
|
||||||
password = row[db.account.password];
|
password = row[db.account.password];
|
||||||
alias = row[db.account.alias];
|
alias = row[db.account.alias];
|
||||||
enabled = row[db.account.enabled];
|
enabled = row[db.account.enabled];
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class Conversation : Object {
|
||||||
this.type_ = type;
|
this.type_ = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Conversation.from_row(Database db, Qlite.Row row) {
|
public Conversation.from_row(Database db, Qlite.Row row) throws InvalidJidError {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|
||||||
id = row[db.conversation.id];
|
id = row[db.conversation.id];
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class FileTransfer : Object {
|
||||||
private Database? db;
|
private Database? db;
|
||||||
private string storage_dir;
|
private string storage_dir;
|
||||||
|
|
||||||
public FileTransfer.from_row(Database db, Qlite.Row row, string storage_dir) {
|
public FileTransfer.from_row(Database db, Qlite.Row row, string storage_dir) throws InvalidJidError {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.storage_dir = storage_dir;
|
this.storage_dir = storage_dir;
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class Message : Object {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message.from_row(Database db, Qlite.Row row) {
|
public Message.from_row(Database db, Qlite.Row row) throws InvalidJidError {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|
||||||
id = row[db.message.id];
|
id = row[db.message.id];
|
||||||
|
|
|
@ -49,19 +49,29 @@ public class ContentItemStore : StreamInteractionModule, Object {
|
||||||
case 1:
|
case 1:
|
||||||
RowOption row_option = db.message.select().with(db.message.id, "=", foreign_id).row();
|
RowOption row_option = db.message.select().with(db.message.id, "=", foreign_id).row();
|
||||||
if (row_option.is_present()) {
|
if (row_option.is_present()) {
|
||||||
Message message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(foreign_id, conversation);
|
Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(foreign_id, conversation);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
|
try {
|
||||||
message = new Message.from_row(db, row_option.inner);
|
message = new Message.from_row(db, row_option.inner);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring message with invalid Jid: %s", e.message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (message != null) {
|
||||||
items.add(new MessageItem(message, conversation, row[db.content_item.id]));
|
items.add(new MessageItem(message, conversation, row[db.content_item.id]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
RowOption row_option = db.file_transfer.select().with(db.file_transfer.id, "=", foreign_id).row();
|
RowOption row_option = db.file_transfer.select().with(db.file_transfer.id, "=", foreign_id).row();
|
||||||
if (row_option.is_present()) {
|
if (row_option.is_present()) {
|
||||||
|
try {
|
||||||
string storage_dir = FileManager.get_storage_dir();
|
string storage_dir = FileManager.get_storage_dir();
|
||||||
FileTransfer file_transfer = new FileTransfer.from_row(db, row_option.inner, storage_dir);
|
FileTransfer file_transfer = new FileTransfer.from_row(db, row_option.inner, storage_dir);
|
||||||
items.add(new FileItem(file_transfer, row[db.content_item.id]));
|
items.add(new FileItem(file_transfer, row[db.content_item.id]));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring file transfer with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -256,7 +266,7 @@ public class FileItem : ContentItem {
|
||||||
public Conversation conversation;
|
public Conversation conversation;
|
||||||
|
|
||||||
public FileItem(FileTransfer file_transfer, int id) {
|
public FileItem(FileTransfer file_transfer, int id) {
|
||||||
Jid jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? file_transfer.account.bare_jid.with_resource(file_transfer.account.resourcepart) : file_transfer.counterpart;
|
Jid jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? file_transfer.account.full_jid : file_transfer.counterpart;
|
||||||
Entities.Message.Marked mark = Entities.Message.Marked.NONE;
|
Entities.Message.Marked mark = Entities.Message.Marked.NONE;
|
||||||
if (file_transfer.direction == FileTransfer.DIRECTION_SENT) {
|
if (file_transfer.direction == FileTransfer.DIRECTION_SENT) {
|
||||||
mark = file_to_message_state(file_transfer.state);
|
mark = file_to_message_state(file_transfer.state);
|
||||||
|
|
|
@ -311,9 +311,13 @@ public class Database : Qlite.Database {
|
||||||
public ArrayList<Account> get_accounts() {
|
public ArrayList<Account> get_accounts() {
|
||||||
ArrayList<Account> ret = new ArrayList<Account>(Account.equals_func);
|
ArrayList<Account> ret = new ArrayList<Account>(Account.equals_func);
|
||||||
foreach(Row row in account.select()) {
|
foreach(Row row in account.select()) {
|
||||||
|
try {
|
||||||
Account account = new Account.from_row(this, row);
|
Account account = new Account.from_row(this, row);
|
||||||
ret.add(account);
|
ret.add(account);
|
||||||
account_table_cache[account.id] = account;
|
account_table_cache[account.id] = account;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring account with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -324,9 +328,13 @@ public class Database : Qlite.Database {
|
||||||
} else {
|
} else {
|
||||||
Row? row = account.row_with(account.id, id).inner;
|
Row? row = account.row_with(account.id, id).inner;
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
|
try {
|
||||||
Account a = new Account.from_row(this, row);
|
Account a = new Account.from_row(this, row);
|
||||||
account_table_cache[a.id] = a;
|
account_table_cache[a.id] = a;
|
||||||
return a;
|
return a;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring account with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -380,7 +388,11 @@ public class Database : Qlite.Database {
|
||||||
|
|
||||||
LinkedList<Message> ret = new LinkedList<Message>();
|
LinkedList<Message> ret = new LinkedList<Message>();
|
||||||
foreach (Row row in select) {
|
foreach (Row row in select) {
|
||||||
|
try {
|
||||||
ret.insert(0, new Message.from_row(this, row));
|
ret.insert(0, new Message.from_row(this, row));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring message with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -394,7 +406,11 @@ public class Database : Qlite.Database {
|
||||||
select.with(message.counterpart_id, "=", get_jid_id(jid));
|
select.with(message.counterpart_id, "=", get_jid_id(jid));
|
||||||
}
|
}
|
||||||
foreach (Row row in select) {
|
foreach (Row row in select) {
|
||||||
|
try {
|
||||||
ret.add(new Message.from_row(this, row));
|
ret.add(new Message.from_row(this, row));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring message with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -402,7 +418,11 @@ public class Database : Qlite.Database {
|
||||||
public Message? get_message_by_id(int id) {
|
public Message? get_message_by_id(int id) {
|
||||||
Row? row = message.row_with(message.id, id).inner;
|
Row? row = message.row_with(message.id, id).inner;
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
|
try {
|
||||||
return new Message.from_row(this, row);
|
return new Message.from_row(this, row);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring message with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -410,7 +430,11 @@ public class Database : Qlite.Database {
|
||||||
public ArrayList<Conversation> get_conversations(Account account) {
|
public ArrayList<Conversation> get_conversations(Account account) {
|
||||||
ArrayList<Conversation> ret = new ArrayList<Conversation>();
|
ArrayList<Conversation> ret = new ArrayList<Conversation>();
|
||||||
foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) {
|
foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) {
|
||||||
|
try {
|
||||||
ret.add(new Conversation.from_row(this, row));
|
ret.add(new Conversation.from_row(this, row));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring conversation with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -426,7 +450,11 @@ public class Database : Qlite.Database {
|
||||||
public HashMap<Jid, string> get_avatar_hashes(int type) {
|
public HashMap<Jid, string> get_avatar_hashes(int type) {
|
||||||
HashMap<Jid, string> ret = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
HashMap<Jid, string> ret = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
||||||
foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) {
|
foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) {
|
||||||
ret[Jid.parse(row[avatar.jid])] = row[avatar.hash];
|
try {
|
||||||
|
ret[new Jid(row[avatar.jid])] = row[avatar.hash];
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring avatar of invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -466,13 +494,13 @@ public class Database : Qlite.Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid? get_jid_by_id(int id) {
|
public Jid? get_jid_by_id(int id) throws InvalidJidError {
|
||||||
if (jid_table_cache.has_key(id)) {
|
if (jid_table_cache.has_key(id)) {
|
||||||
return jid_table_cache[id];
|
return jid_table_cache[id];
|
||||||
} else {
|
} else {
|
||||||
string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid];
|
string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid];
|
||||||
if (bare_jid != null) {
|
if (bare_jid != null) {
|
||||||
Jid jid_parsed = Jid.parse(bare_jid);
|
Jid jid_parsed = new Jid(bare_jid);
|
||||||
jid_table_cache[id] = jid_parsed;
|
jid_table_cache[id] = jid_parsed;
|
||||||
jid_table_reverse[jid_parsed] = id;
|
jid_table_reverse[jid_parsed] = id;
|
||||||
return jid_parsed;
|
return jid_parsed;
|
||||||
|
|
|
@ -169,8 +169,12 @@ public class FileManager : StreamInteractionModule, Object {
|
||||||
private Gee.List<FileTransfer> get_transfers_from_qry(Qlite.QueryBuilder select) {
|
private Gee.List<FileTransfer> get_transfers_from_qry(Qlite.QueryBuilder select) {
|
||||||
Gee.List<FileTransfer> ret = new ArrayList<FileTransfer>();
|
Gee.List<FileTransfer> ret = new ArrayList<FileTransfer>();
|
||||||
foreach (Qlite.Row row in select) {
|
foreach (Qlite.Row row in select) {
|
||||||
|
try {
|
||||||
FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir());
|
FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir());
|
||||||
ret.insert(0, file_transfer);
|
ret.insert(0, file_transfer);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring file transfer with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -287,7 +291,7 @@ public class FileManager : StreamInteractionModule, Object {
|
||||||
if (conversation.type_ in new Conversation.Type[]{Conversation.Type.GROUPCHAT, Conversation.Type.GROUPCHAT_PM}) {
|
if (conversation.type_ in new Conversation.Type[]{Conversation.Type.GROUPCHAT, Conversation.Type.GROUPCHAT_PM}) {
|
||||||
file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
||||||
} else {
|
} else {
|
||||||
file_transfer.ourpart = conversation.account.bare_jid.with_resource(conversation.account.resourcepart);
|
file_transfer.ourpart = conversation.account.full_jid;
|
||||||
}
|
}
|
||||||
file_transfer.time = time;
|
file_transfer.time = time;
|
||||||
file_transfer.local_time = local_time;
|
file_transfer.local_time = local_time;
|
||||||
|
|
|
@ -534,7 +534,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||||
message.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
message.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid;
|
||||||
message.real_jid = conversation.account.bare_jid;
|
message.real_jid = conversation.account.bare_jid;
|
||||||
} else {
|
} else {
|
||||||
message.ourpart = conversation.account.bare_jid.with_resource(conversation.account.resourcepart);
|
message.ourpart = conversation.account.full_jid;
|
||||||
}
|
}
|
||||||
message.marked = Entities.Message.Marked.UNSENT;
|
message.marked = Entities.Message.Marked.UNSENT;
|
||||||
message.encryption = conversation.encryption;
|
message.encryption = conversation.encryption;
|
||||||
|
|
|
@ -239,11 +239,15 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid? get_own_jid(Jid muc_jid, Account account) {
|
public Jid? get_own_jid(Jid muc_jid, Account account) {
|
||||||
|
try {
|
||||||
Xep.Muc.Flag? flag = get_muc_flag(account);
|
Xep.Muc.Flag? flag = get_muc_flag(account);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
string? nick = flag.get_muc_nick(muc_jid);
|
string? nick = flag.get_muc_nick(muc_jid);
|
||||||
if (nick != null) return muc_jid.with_resource(nick);
|
if (nick != null) return muc_jid.with_resource(nick);
|
||||||
}
|
}
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Joined MUC with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,11 +91,15 @@ public class RosterStoreImpl : Roster.Storage, Object {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|
||||||
foreach (Qlite.Row row in db.roster.select().with(db.roster.account_id, "=", account.id)) {
|
foreach (Qlite.Row row in db.roster.select().with(db.roster.account_id, "=", account.id)) {
|
||||||
|
try {
|
||||||
Roster.Item item = new Roster.Item();
|
Roster.Item item = new Roster.Item();
|
||||||
item.jid = Jid.parse(row[db.roster.jid]);
|
item.jid = new Jid(row[db.roster.jid]);
|
||||||
item.name = row[db.roster.handle];
|
item.name = row[db.roster.handle];
|
||||||
item.subscription = row[db.roster.subscription];
|
item.subscription = row[db.roster.subscription];
|
||||||
items[item.jid] = item;
|
items[item.jid] = item;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring roster entry with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,10 +132,14 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
.with(db.conversation.type_, "=", Conversation.Type.CHAT)
|
.with(db.conversation.type_, "=", Conversation.Type.CHAT)
|
||||||
.order_by(db.conversation.last_active, "DESC");
|
.order_by(db.conversation.last_active, "DESC");
|
||||||
foreach(Row chat in chats) {
|
foreach(Row chat in chats) {
|
||||||
|
try {
|
||||||
if (suggestions.size == 0) {
|
if (suggestions.size == 0) {
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space));
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space));
|
||||||
}
|
}
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space));
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search suggestion with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
@ -155,7 +159,11 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
.order_by_name(@"MAX($(db.message.time))", "DESC")
|
.order_by_name(@"MAX($(db.message.time))", "DESC")
|
||||||
.limit(5);
|
.limit(5);
|
||||||
foreach(Row msg in msgs) {
|
foreach(Row msg in msgs) {
|
||||||
|
try {
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space));
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search suggestion with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: auto complete from
|
// TODO: auto complete from
|
||||||
|
@ -181,7 +189,11 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
.order_by(db.conversation.last_active, "DESC")
|
.order_by(db.conversation.last_active, "DESC")
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
foreach(Row chat in chats) {
|
foreach(Row chat in chats) {
|
||||||
|
try {
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]});
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]});
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search suggestion with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Groupchat PM
|
// Groupchat PM
|
||||||
|
@ -195,7 +207,11 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
.order_by(db.conversation.last_active, "DESC")
|
.order_by(db.conversation.last_active, "DESC")
|
||||||
.limit(limit - suggestions.size);
|
.limit(limit - suggestions.size);
|
||||||
foreach(Row chat in chats) {
|
foreach(Row chat in chats) {
|
||||||
|
try {
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]});
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]});
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search suggestion with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
suggestions.sort((a, b) => (int)(b.order - a.order));
|
suggestions.sort((a, b) => (int)(b.order - a.order));
|
||||||
}
|
}
|
||||||
|
@ -218,7 +234,11 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
.order_by(db.conversation.last_active, "DESC")
|
.order_by(db.conversation.last_active, "DESC")
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
foreach(Row chat in groupchats) {
|
foreach(Row chat in groupchats) {
|
||||||
|
try {
|
||||||
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space));
|
suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search suggestion with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Other auto complete?
|
// Other auto complete?
|
||||||
|
@ -233,9 +253,13 @@ public class SearchProcessor : StreamInteractionModule, Object {
|
||||||
rows.offset(offset);
|
rows.offset(offset);
|
||||||
}
|
}
|
||||||
foreach (Row row in rows) {
|
foreach (Row row in rows) {
|
||||||
|
try {
|
||||||
Message message = new Message.from_row(db, row);
|
Message message = new Message.from_row(db, row);
|
||||||
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message);
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message);
|
||||||
ret.add(new MessageItem(message, conversation, row[db.content_item.id]));
|
ret.add(new MessageItem(message, conversation, row[db.content_item.id]));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring search result with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TAR
|
||||||
|
|
||||||
find_packages(MAIN_PACKAGES REQUIRED
|
find_packages(MAIN_PACKAGES REQUIRED
|
||||||
Gee
|
Gee
|
||||||
GLib>=2.38
|
GLib
|
||||||
GModule
|
GModule
|
||||||
GObject
|
GObject
|
||||||
GTK3>=3.22
|
GTK3
|
||||||
ICU>=57
|
ICU
|
||||||
)
|
)
|
||||||
|
|
||||||
set(RESOURCE_LIST
|
set(RESOURCE_LIST
|
||||||
|
|
|
@ -159,7 +159,7 @@ public class AddConferenceDialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_ok_sensitive_from_details() {
|
private void set_ok_sensitive_from_details() {
|
||||||
ok_button.sensitive = select_fragment.done;
|
ok_button.sensitive = details_fragment.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_next_button_clicked() {
|
private void on_next_button_clicked() {
|
||||||
|
|
|
@ -39,17 +39,24 @@ protected class AddContactDialog : Gtk.Dialog {
|
||||||
|
|
||||||
private void on_ok_button_clicked() {
|
private void on_ok_button_clicked() {
|
||||||
string? alias = alias_entry.text == "" ? null : alias_entry.text;
|
string? alias = alias_entry.text == "" ? null : alias_entry.text;
|
||||||
|
try {
|
||||||
Jid jid = new Jid(jid_entry.text);
|
Jid jid = new Jid(jid_entry.text);
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).add_jid(account, jid, alias);
|
stream_interactor.get_module(RosterManager.IDENTITY).add_jid(account, jid, alias);
|
||||||
stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(account, jid);
|
stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(account, jid);
|
||||||
close();
|
close();
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Tried to add contact with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_jid_entry_changed() {
|
private void on_jid_entry_changed() {
|
||||||
Jid parsed_jid = Jid.parse(jid_entry.text);
|
try {
|
||||||
bool sensitive = parsed_jid != null && parsed_jid.resourcepart == null &&
|
Jid parsed_jid = new Jid(jid_entry.text);
|
||||||
|
ok_button. sensitive = parsed_jid != null && parsed_jid.resourcepart == null &&
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, parsed_jid) == null;
|
stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, parsed_jid) == null;
|
||||||
ok_button.set_sensitive(sensitive);
|
} catch (InvalidJidError e) {
|
||||||
|
ok_button.sensitive = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,21 +52,30 @@ protected class AddGroupchatDialog : Gtk.Dialog {
|
||||||
private bool on_jid_key_release() {
|
private bool on_jid_key_release() {
|
||||||
check_ok();
|
check_ok();
|
||||||
if (!alias_entry_changed) {
|
if (!alias_entry_changed) {
|
||||||
Jid? parsed_jid = Jid.parse(jid_entry.text);
|
try {
|
||||||
|
Jid parsed_jid = new Jid(jid_entry.text);
|
||||||
alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text;
|
alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
alias_entry.text = jid_entry.text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool check_ok() {
|
private bool check_ok() {
|
||||||
Jid? parsed_jid = Jid.parse(jid_entry.text);
|
try {
|
||||||
|
Jid parsed_jid = new Jid(jid_entry.text);
|
||||||
ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null;
|
ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
ok_button.sensitive = false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_ok_button_clicked() {
|
private void on_ok_button_clicked() {
|
||||||
|
try {
|
||||||
Conference conference = new Conference();
|
Conference conference = new Conference();
|
||||||
conference.jid = Jid.parse(jid_entry.text);
|
conference.jid = new Jid(jid_entry.text);
|
||||||
conference.nick = nick_entry.text != "" ? nick_entry.text : null;
|
conference.nick = nick_entry.text != "" ? nick_entry.text : null;
|
||||||
conference.name = alias_entry.text;
|
conference.name = alias_entry.text;
|
||||||
if (edit_conference == null) {
|
if (edit_conference == null) {
|
||||||
|
@ -75,6 +84,9 @@ protected class AddGroupchatDialog : Gtk.Dialog {
|
||||||
stream_interactor.get_module(MucManager.IDENTITY).replace_bookmark(account_combobox.selected, edit_conference, conference);
|
stream_interactor.get_module(MucManager.IDENTITY).replace_bookmark(account_combobox.selected, edit_conference, conference);
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid conference Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,12 @@ protected class ConferenceDetailsFragment : Box {
|
||||||
|
|
||||||
public bool done {
|
public bool done {
|
||||||
get {
|
get {
|
||||||
Jid? parsed_jid = Jid.parse(jid);
|
try {
|
||||||
return parsed_jid != null && parsed_jid.localpart != null &&
|
Jid parsed_jid = new Jid(jid);
|
||||||
parsed_jid.resourcepart == null && nick != "";
|
return parsed_jid.localpart != null && parsed_jid.resourcepart == null && nick != null;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private set {}
|
private set {}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +149,8 @@ protected class ConferenceDetailsFragment : Box {
|
||||||
ok_button.label = _("Joining…");
|
ok_button.label = _("Joining…");
|
||||||
ok_button.sensitive = false;
|
ok_button.sensitive = false;
|
||||||
|
|
||||||
|
string label_text = "";
|
||||||
|
try {
|
||||||
Jid parsed_jid = new Jid(jid);
|
Jid parsed_jid = new Jid(jid);
|
||||||
Muc.JoinResult? join_result = yield stream_interactor.get_module(MucManager.IDENTITY).join(account, parsed_jid, nick, password);
|
Muc.JoinResult? join_result = yield stream_interactor.get_module(MucManager.IDENTITY).join(account, parsed_jid, nick, password);
|
||||||
|
|
||||||
|
@ -154,12 +159,10 @@ protected class ConferenceDetailsFragment : Box {
|
||||||
if (join_result == null || join_result.nick != null) {
|
if (join_result == null || join_result.nick != null) {
|
||||||
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, account, Conversation.Type.GROUPCHAT);
|
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, account, Conversation.Type.GROUPCHAT);
|
||||||
Application app = GLib.Application.get_default() as Application;
|
Application app = GLib.Application.get_default() as Application;
|
||||||
app.controller.select_conversation(conversation);
|
app.controller.select_conversation(conversation);joined();
|
||||||
joined();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string label_text = "";
|
|
||||||
if (join_result.muc_error != null) {
|
if (join_result.muc_error != null) {
|
||||||
switch (join_result.muc_error) {
|
switch (join_result.muc_error) {
|
||||||
case Muc.MucEnterError.PASSWORD_REQUIRED:
|
case Muc.MucEnterError.PASSWORD_REQUIRED:
|
||||||
|
@ -184,6 +187,9 @@ protected class ConferenceDetailsFragment : Box {
|
||||||
} else if (join_result.stanza_error != null) {
|
} else if (join_result.stanza_error != null) {
|
||||||
label_text = _("Could not connect to %s").printf((new Jid(jid)).domainpart);
|
label_text = _("Could not connect to %s").printf((new Jid(jid)).domainpart);
|
||||||
}
|
}
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
label_text = _("Invalid address");
|
||||||
|
}
|
||||||
notification_label.label = label_text;
|
notification_label.label = label_text;
|
||||||
notification_revealer.set_reveal_child(true);
|
notification_revealer.set_reveal_child(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,14 +53,18 @@ public class SelectJidFragment : Gtk.Box {
|
||||||
|
|
||||||
string[] ? values = str == "" ? null : str.split(" ");
|
string[] ? values = str == "" ? null : str.split(" ");
|
||||||
filterable_list.set_filter_values(values);
|
filterable_list.set_filter_values(values);
|
||||||
Jid? parsed_jid = Jid.parse(str);
|
try {
|
||||||
|
Jid parsed_jid = new Jid(str);
|
||||||
if (parsed_jid != null && parsed_jid.localpart != null) {
|
if (parsed_jid != null && parsed_jid.localpart != null) {
|
||||||
foreach (Account account in accounts) {
|
foreach (Account account in accounts) {
|
||||||
AddListRow row = new AddListRow(stream_interactor, str, account);
|
AddListRow row = new AddListRow(stream_interactor, parsed_jid, account);
|
||||||
filterable_list.add(row);
|
filterable_list.add(row);
|
||||||
added_rows.add(row);
|
added_rows.add(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (InvalidJidError ignored) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void check_buttons_active() {
|
private void check_buttons_active() {
|
||||||
|
@ -82,11 +86,11 @@ public class SelectJidFragment : Gtk.Box {
|
||||||
|
|
||||||
private class AddListRow : ListRow {
|
private class AddListRow : ListRow {
|
||||||
|
|
||||||
public AddListRow(StreamInteractor stream_interactor, string jid, Account account) {
|
public AddListRow(StreamInteractor stream_interactor, Jid jid, Account account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.jid = new Jid(jid);
|
this.jid = jid;
|
||||||
|
|
||||||
name_label.label = jid;
|
name_label.label = jid.to_string();
|
||||||
if (stream_interactor.get_accounts().size > 1) {
|
if (stream_interactor.get_accounts().size > 1) {
|
||||||
via_label.label = account.bare_jid.to_string();
|
via_label.label = account.bare_jid.to_string();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -50,12 +50,18 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
|
||||||
public void handle_uri(string jid, string query, Gee.Map<string, string> options) {
|
public void handle_uri(string jid, string query, Gee.Map<string, string> options) {
|
||||||
switch (query) {
|
switch (query) {
|
||||||
case "join":
|
case "join":
|
||||||
show_join_muc_dialog(null, new Jid(jid));
|
show_join_muc_dialog(null, jid);
|
||||||
break;
|
break;
|
||||||
case "message":
|
case "message":
|
||||||
Gee.List<Account> accounts = stream_interactor.get_accounts();
|
Gee.List<Account> accounts = stream_interactor.get_accounts();
|
||||||
if (accounts.size == 1) {
|
Jid parsed_jid = null;
|
||||||
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(new Jid(jid), accounts[0], Conversation.Type.CHAT);
|
try {
|
||||||
|
parsed_jid = new Jid(jid);
|
||||||
|
} catch (InvalidJidError ignored) {
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
if (accounts.size == 1 && parsed_jid != null) {
|
||||||
|
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, accounts[0], Conversation.Type.CHAT);
|
||||||
stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation);
|
stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation);
|
||||||
controller.select_conversation(conversation);
|
controller.select_conversation(conversation);
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,7 +134,7 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
|
||||||
accept_muc_invite_action.activate.connect((variant) => {
|
accept_muc_invite_action.activate.connect((variant) => {
|
||||||
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32());
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32());
|
||||||
if (conversation == null) return;
|
if (conversation == null) return;
|
||||||
show_join_muc_dialog(conversation.account, conversation.counterpart);
|
show_join_muc_dialog(conversation.account, conversation.counterpart.to_string());
|
||||||
});
|
});
|
||||||
add_action(accept_muc_invite_action);
|
add_action(accept_muc_invite_action);
|
||||||
|
|
||||||
|
@ -182,13 +188,13 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
|
||||||
license_type: License.GPL_3_0);
|
license_type: License.GPL_3_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show_join_muc_dialog(Account? account, Jid jid) {
|
private void show_join_muc_dialog(Account? account, string jid) {
|
||||||
Dialog dialog = new Dialog.with_buttons(_("Join Channel"), window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.USE_HEADER_BAR, _("Join"), ResponseType.OK, _("Cancel"), ResponseType.CANCEL);
|
Dialog dialog = new Dialog.with_buttons(_("Join Channel"), window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.USE_HEADER_BAR, _("Join"), ResponseType.OK, _("Cancel"), ResponseType.CANCEL);
|
||||||
dialog.modal = true;
|
dialog.modal = true;
|
||||||
Button ok_button = dialog.get_widget_for_response(ResponseType.OK) as Button;
|
Button ok_button = dialog.get_widget_for_response(ResponseType.OK) as Button;
|
||||||
ok_button.get_style_context().add_class("suggested-action");
|
ok_button.get_style_context().add_class("suggested-action");
|
||||||
ConferenceDetailsFragment conference_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button };
|
ConferenceDetailsFragment conference_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button };
|
||||||
conference_fragment.jid = jid.to_string();
|
conference_fragment.jid = jid;
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
conference_fragment.account = account;
|
conference_fragment.account = account;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,11 @@ public class ChatInputController : Object {
|
||||||
return;
|
return;
|
||||||
case "/ping":
|
case "/ping":
|
||||||
Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account);
|
||||||
|
try {
|
||||||
stream.get_module(Xmpp.Xep.Ping.Module.IDENTITY).send_ping(stream, conversation.counterpart.with_resource(token[1]), null);
|
stream.get_module(Xmpp.Xep.Ping.Module.IDENTITY).send_ping(stream, conversation.counterpart.with_resource(token[1]), null);
|
||||||
|
} catch (Xmpp.InvalidJidError e) {
|
||||||
|
warning("Could not ping invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case "/topic":
|
case "/topic":
|
||||||
stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, token[1]);
|
stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, token[1]);
|
||||||
|
|
|
@ -3,7 +3,6 @@ using Gdk;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Pango;
|
using Pango;
|
||||||
using Xmpp;
|
using Xmpp;
|
||||||
using Unicode;
|
|
||||||
|
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
private Database db;
|
private Database db;
|
||||||
private HashMap<ListBoxRow, string> list_box_jids = new HashMap<ListBoxRow, string>();
|
private HashMap<ListBoxRow, string> list_box_jids = new HashMap<ListBoxRow, string>();
|
||||||
private Jid? server_jid = null;
|
private Jid? server_jid = null;
|
||||||
|
private Jid? login_jid = null;
|
||||||
private Xep.InBandRegistration.Form? form = null;
|
private Xep.InBandRegistration.Form? form = null;
|
||||||
|
|
||||||
public AddAccountDialog(StreamInteractor stream_interactor, Database db) {
|
public AddAccountDialog(StreamInteractor stream_interactor, Database db) {
|
||||||
|
@ -97,8 +98,12 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
|
|
||||||
// Select Server
|
// Select Server
|
||||||
server_entry.changed.connect(() => {
|
server_entry.changed.connect(() => {
|
||||||
Jid? jid = Jid.parse(server_entry.text);
|
try {
|
||||||
|
Jid jid = new Jid(server_entry.text);
|
||||||
select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null;
|
select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
select_server_continue.sensitive = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
select_server_continue.clicked.connect(on_select_server_continue);
|
select_server_continue.clicked.connect(on_select_server_continue);
|
||||||
login_button.clicked.connect(show_sign_in_jid);
|
login_button.clicked.connect(show_sign_in_jid);
|
||||||
|
@ -131,6 +136,7 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
set_default(sign_in_jid_continue_button);
|
set_default(sign_in_jid_continue_button);
|
||||||
|
|
||||||
sign_in_jid_error_label.label = "";
|
sign_in_jid_error_label.label = "";
|
||||||
|
jid_entry.sensitive = true;
|
||||||
animate_window_resize(sign_in_jid_box);
|
animate_window_resize(sign_in_jid_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +161,7 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
set_default(sign_in_password_continue_button);
|
set_default(sign_in_password_continue_button);
|
||||||
|
|
||||||
sign_in_password_error_label.label = "";
|
sign_in_password_error_label.label = "";
|
||||||
sign_in_password_title.label = _("Sign in to %s").printf(jid_entry.text);
|
sign_in_password_title.label = _("Sign in to %s").printf(login_jid.to_string());
|
||||||
animate_window_resize(sign_in_password_box);
|
animate_window_resize(sign_in_password_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,25 +211,36 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_jid_entry_changed() {
|
private void on_jid_entry_changed() {
|
||||||
Jid? jid = Jid.parse(jid_entry.text);
|
try {
|
||||||
if (jid != null && jid.localpart != null && jid.resourcepart == null) {
|
login_jid = new Jid(jid_entry.text);
|
||||||
sign_in_jid_continue_button.set_sensitive(true);
|
if (login_jid.localpart != null && login_jid.resourcepart == null) {
|
||||||
|
sign_in_jid_continue_button.sensitive = true;
|
||||||
jid_entry.secondary_icon_name = null;
|
jid_entry.secondary_icon_name = null;
|
||||||
} else {
|
} else {
|
||||||
sign_in_jid_continue_button.set_sensitive(false);
|
sign_in_jid_continue_button.sensitive = false;
|
||||||
|
}
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
sign_in_jid_continue_button.sensitive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void on_sign_in_jid_continue_button_clicked() {
|
private async void on_sign_in_jid_continue_button_clicked() {
|
||||||
Jid jid = new Jid(jid_entry.get_text());
|
try {
|
||||||
|
login_jid = new Jid(jid_entry.text);
|
||||||
|
jid_entry.sensitive = false;
|
||||||
|
sign_in_tls_label.label = "";
|
||||||
|
sign_in_jid_error_label.label = "";
|
||||||
|
sign_in_jid_continue_button.sensitive = false;
|
||||||
sign_in_jid_continue_stack.visible_child_name = "spinner";
|
sign_in_jid_continue_stack.visible_child_name = "spinner";
|
||||||
Register.ServerAvailabilityReturn server_status = yield Register.check_server_availability(jid);
|
Register.ServerAvailabilityReturn server_status = yield Register.check_server_availability(login_jid);
|
||||||
sign_in_jid_continue_stack.visible_child_name = "label";
|
sign_in_jid_continue_stack.visible_child_name = "label";
|
||||||
|
sign_in_jid_continue_button.sensitive = true;
|
||||||
if (server_status.available) {
|
if (server_status.available) {
|
||||||
show_sign_in_password();
|
show_sign_in_password();
|
||||||
} else {
|
} else {
|
||||||
|
jid_entry.sensitive = true;
|
||||||
if (server_status.error_flags != null) {
|
if (server_status.error_flags != null) {
|
||||||
string error_desc = "The server could not prove that it is %s.".printf("<b>" + jid.domainpart + "</b>");
|
string error_desc = "The server could not prove that it is %s.".printf("<b>" + login_jid.domainpart + "</b>");
|
||||||
if (TlsCertificateFlags.UNKNOWN_CA in server_status.error_flags) {
|
if (TlsCertificateFlags.UNKNOWN_CA in server_status.error_flags) {
|
||||||
error_desc += " " + "Its security certificate is not trusted by your computer's operating system.";
|
error_desc += " " + "Its security certificate is not trusted by your computer's operating system.";
|
||||||
} else if (TlsCertificateFlags.BAD_IDENTITY in server_status.error_flags) {
|
} else if (TlsCertificateFlags.BAD_IDENTITY in server_status.error_flags) {
|
||||||
|
@ -236,15 +253,18 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
sign_in_tls_label.label = error_desc;
|
sign_in_tls_label.label = error_desc;
|
||||||
show_tls_error();
|
show_tls_error();
|
||||||
} else {
|
} else {
|
||||||
sign_in_jid_error_label.label = _("Could not connect to %s").printf(jid.domainpart);
|
sign_in_jid_error_label.label = _("Could not connect to %s").printf(login_jid.domainpart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Invalid address from interface allowed login: %s", e.message);
|
||||||
|
sign_in_jid_error_label.label = _("Invalid address");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void on_sign_in_password_continue_button_clicked() {
|
private async void on_sign_in_password_continue_button_clicked() {
|
||||||
Jid jid = new Jid(jid_entry.get_text());
|
string password = password_entry.text;
|
||||||
string password = password_entry.get_text();
|
Account account = new Account(login_jid, null, password, null);
|
||||||
Account account = new Account(jid, null, password, null);
|
|
||||||
|
|
||||||
sign_in_password_continue_stack.visible_child_name = "spinner";
|
sign_in_password_continue_stack.visible_child_name = "spinner";
|
||||||
ConnectionManager.ConnectionError.Source? error = yield stream_interactor.get_module(Register.IDENTITY).add_check_account(account);
|
ConnectionManager.ConnectionError.Source? error = yield stream_interactor.get_module(Register.IDENTITY).add_check_account(account);
|
||||||
|
@ -256,7 +276,7 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
sign_in_password_error_label.label = _("Wrong username or password");
|
sign_in_password_error_label.label = _("Wrong username or password");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sign_in_password_error_label.label = "Something went wrong";
|
sign_in_password_error_label.label = _("Something went wrong");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -266,13 +286,23 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_select_server_continue() {
|
private void on_select_server_continue() {
|
||||||
|
try {
|
||||||
server_jid = new Jid(server_entry.text);
|
server_jid = new Jid(server_entry.text);
|
||||||
request_show_register_form.begin();
|
request_show_register_form.begin();
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Invalid address from interface allowed server: %s", e.message);
|
||||||
|
display_notification(_("Invalid address"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_server_list_row_selected(ListBox box, ListBoxRow? row) {
|
private void on_server_list_row_selected(ListBox box, ListBoxRow? row) {
|
||||||
|
try {
|
||||||
server_jid = new Jid(list_box_jids[row]);
|
server_jid = new Jid(list_box_jids[row]);
|
||||||
request_show_register_form.begin();
|
request_show_register_form.begin();
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Invalid address from selected server: %s", e.message);
|
||||||
|
display_notification(_("Invalid address"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void request_show_register_form() {
|
private async void request_show_register_form() {
|
||||||
|
@ -341,9 +371,14 @@ public class AddAccountDialog : Gtk.Dialog {
|
||||||
case "password": password = field.get_value_string(); break;
|
case "password": password = field.get_value_string(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
Account account = new Account(new Jid.components(username, server_jid.domainpart, null), null, password, null);
|
Account account = new Account(new Jid.components(username, server_jid.domainpart, null), null, password, null);
|
||||||
add_activate_account(account);
|
add_activate_account(account);
|
||||||
show_success(account);
|
show_success(account);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Invalid address from components of registration: %s", e.message);
|
||||||
|
display_notification(_("Invalid address"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
display_notification(error);
|
display_notification(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -331,11 +331,11 @@ public int get_only_emoji_count(string markup_text) {
|
||||||
emoji_no--;
|
emoji_no--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (last_was_emoji && last_was_modifier_base && Unicode.has_binary_property(curchar, Unicode.Property.EMOJI_MODIFIER)) {
|
if (last_was_emoji && last_was_modifier_base && ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER)) {
|
||||||
// still an emoji, but no longer a modifier base
|
// still an emoji, but no longer a modifier base
|
||||||
last_was_modifier_base = false;
|
last_was_modifier_base = false;
|
||||||
} else if (Unicode.has_binary_property(curchar, Unicode.Property.EMOJI_PRESENTATION)) {
|
} else if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_PRESENTATION)) {
|
||||||
if (Unicode.has_binary_property(curchar, Unicode.Property.EMOJI_MODIFIER_BASE)) {
|
if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER_BASE)) {
|
||||||
last_was_modifier_base = true;
|
last_was_modifier_base = true;
|
||||||
}
|
}
|
||||||
emoji_no++;
|
emoji_no++;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
namespace Unicode {
|
namespace ICU {
|
||||||
|
|
||||||
[CCode (cprefix = "UCHAR_", cheader_filename = "unicode/uchar.h")]
|
[CCode (cprefix = "UCHAR_", cheader_filename = "unicode/uchar.h")]
|
||||||
public enum Property {
|
public enum Property {
|
||||||
EMOJI,
|
EMOJI,
|
||||||
|
@ -9,4 +10,5 @@ namespace Unicode {
|
||||||
|
|
||||||
[CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")]
|
[CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")]
|
||||||
public bool has_binary_property(unichar c, Property p);
|
public bool has_binary_property(unichar c, Property p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,8 +202,12 @@ public class Manager : StreamInteractionModule, Object {
|
||||||
//Fetch the bundle for each new device
|
//Fetch the bundle for each new device
|
||||||
int inc = 0;
|
int inc = 0;
|
||||||
foreach (Row row in db.identity_meta.get_unknown_devices(identity_id, jid.bare_jid.to_string())) {
|
foreach (Row row in db.identity_meta.get_unknown_devices(identity_id, jid.bare_jid.to_string())) {
|
||||||
module.fetch_bundle(stream, Jid.parse(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false);
|
try {
|
||||||
|
module.fetch_bundle(stream, new Jid(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false);
|
||||||
inc++;
|
inc++;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring device with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (inc > 0) {
|
if (inc > 0) {
|
||||||
debug("new bundles %i/%i for %s", inc, device_list.size, jid.to_string());
|
debug("new bundles %i/%i for %s", inc, device_list.size, jid.to_string());
|
||||||
|
|
|
@ -303,7 +303,11 @@ public class TrustManager {
|
||||||
PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content));
|
PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content));
|
||||||
string identity_key = Base64.encode(msg.identity_key.serialize());
|
string identity_key = Base64.encode(msg.identity_key.serialize());
|
||||||
foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid).with(db.identity_meta.identity_key_public_base64, "=", identity_key)) {
|
foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid).with(db.identity_meta.identity_key_public_base64, "=", identity_key)) {
|
||||||
|
try {
|
||||||
possible_jids.add(new Jid(row[db.identity_meta.address_name]));
|
possible_jids.add(new Jid(row[db.identity_meta.address_name]));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid jid from database: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (possible_jids.size != 1) {
|
if (possible_jids.size != 1) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -311,7 +315,11 @@ public class TrustManager {
|
||||||
} else {
|
} else {
|
||||||
// If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id
|
// If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id
|
||||||
foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid)) {
|
foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid)) {
|
||||||
|
try {
|
||||||
possible_jids.add(new Jid(row[db.identity_meta.address_name]));
|
possible_jids.add(new Jid(row[db.identity_meta.address_name]));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid jid from database: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,11 @@ public class ContactDetailsDialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) {
|
foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) {
|
||||||
module.fetch_bundle(stream, Jid.parse(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false);
|
try {
|
||||||
|
module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring device with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ find_packages(ENGINE_PACKAGES REQUIRED
|
||||||
GIO
|
GIO
|
||||||
GLib
|
GLib
|
||||||
GObject
|
GObject
|
||||||
|
ICU
|
||||||
)
|
)
|
||||||
|
|
||||||
set(ENGINE_DEFINITIONS "")
|
set(ENGINE_DEFINITIONS "")
|
||||||
|
@ -15,6 +16,7 @@ if(GIO_VERSION VERSION_GREATER "2.60")
|
||||||
else()
|
else()
|
||||||
message(STATUS "No ALPN support, needs GIO >= 2.60")
|
message(STATUS "No ALPN support, needs GIO >= 2.60")
|
||||||
endif()
|
endif()
|
||||||
|
set(ENGINE_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi)
|
||||||
|
|
||||||
vala_precompile(ENGINE_VALA_C
|
vala_precompile(ENGINE_VALA_C
|
||||||
SOURCES
|
SOURCES
|
||||||
|
@ -104,6 +106,8 @@ CUSTOM_VAPIS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi"
|
||||||
DEFINITIONS
|
DEFINITIONS
|
||||||
${ENGINE_DEFINITIONS}
|
${ENGINE_DEFINITIONS}
|
||||||
|
OPTIONS
|
||||||
|
${ENGINE_EXTRA_OPTIONS}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(xmpp-vala-vapi
|
add_custom_target(xmpp-vala-vapi
|
||||||
|
@ -128,12 +132,15 @@ if(BUILD_TESTS)
|
||||||
"tests/common.vala"
|
"tests/common.vala"
|
||||||
"tests/testcase.vala"
|
"tests/testcase.vala"
|
||||||
|
|
||||||
|
"tests/jid.vala"
|
||||||
"tests/stanza.vala"
|
"tests/stanza.vala"
|
||||||
"tests/util.vala"
|
"tests/util.vala"
|
||||||
CUSTOM_VAPIS
|
CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
||||||
PACKAGES
|
PACKAGES
|
||||||
${ENGINE_PACKAGES}
|
${ENGINE_PACKAGES}
|
||||||
|
OPTIONS
|
||||||
|
${ENGINE_EXTRA_OPTIONS}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(${VALA_CFLAGS})
|
add_definitions(${VALA_CFLAGS})
|
||||||
|
|
|
@ -44,7 +44,11 @@ public class XmppStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void connect(string? remote_name = null) throws IOStreamError {
|
public async void connect(string? remote_name = null) throws IOStreamError {
|
||||||
if (remote_name != null) this.remote_name = Jid.parse(remote_name);
|
try {
|
||||||
|
if (remote_name != null) this.remote_name = new Jid(remote_name);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
throw new IOStreamError.CONNECT(@"Invalid remote name \"$remote_name\": $(e.message)");
|
||||||
|
}
|
||||||
attach_negotation_modules();
|
attach_negotation_modules();
|
||||||
try {
|
try {
|
||||||
int min_priority = -1;
|
int min_priority = -1;
|
||||||
|
|
|
@ -5,11 +5,11 @@ namespace Xmpp.Bind {
|
||||||
public class Module : XmppStreamNegotiationModule {
|
public class Module : XmppStreamNegotiationModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "bind_module");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "bind_module");
|
||||||
|
|
||||||
public string requested_resource { get; set; }
|
public string? requested_resource { get; set; }
|
||||||
|
|
||||||
public signal void bound_to_resource(XmppStream stream, Jid my_jid);
|
public signal void bound_to_resource(XmppStream stream, Jid my_jid);
|
||||||
|
|
||||||
public Module(string requested_resource) {
|
public Module(string? requested_resource) {
|
||||||
this.requested_resource = requested_resource;
|
this.requested_resource = requested_resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +18,13 @@ namespace Xmpp.Bind {
|
||||||
if (flag == null || flag.finished) return;
|
if (flag == null || flag.finished) return;
|
||||||
|
|
||||||
if (iq.type_ == Iq.Stanza.TYPE_RESULT) {
|
if (iq.type_ == Iq.Stanza.TYPE_RESULT) {
|
||||||
flag.my_jid = Jid.parse(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content());
|
try {
|
||||||
|
flag.my_jid = new Jid(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content());
|
||||||
flag.finished = true;
|
flag.finished = true;
|
||||||
bound_to_resource(stream, flag.my_jid);
|
bound_to_resource(stream, flag.my_jid);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received invalid Jid when binding: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +36,9 @@ namespace Xmpp.Bind {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
var flag = new Flag();
|
var flag = new Flag();
|
||||||
StanzaNode bind_node = new StanzaNode.build("bind", NS_URI).add_self_xmlns();
|
StanzaNode bind_node = new StanzaNode.build("bind", NS_URI).add_self_xmlns();
|
||||||
|
if (requested_resource != null) {
|
||||||
bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource)));
|
bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource)));
|
||||||
|
}
|
||||||
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.set(bind_node), iq_response_stanza);
|
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.set(bind_node), iq_response_stanza);
|
||||||
stream.add_flag(flag);
|
stream.add_flag(flag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
namespace Xmpp {
|
namespace Xmpp {
|
||||||
|
|
||||||
public class Conference : Object {
|
public class Conference : Object {
|
||||||
public virtual Jid jid { get; set; }
|
public virtual Jid? jid { get; set; }
|
||||||
public virtual bool autojoin { get; set; }
|
public virtual bool autojoin { get; set; }
|
||||||
public virtual string? nick { get; set; }
|
public virtual string? nick { get; set; }
|
||||||
public virtual string? name { get; set; }
|
public virtual string? name { get; set; }
|
||||||
public virtual string? password { get; set; }
|
public virtual string? password { get; set; }
|
||||||
|
|
||||||
public static bool equal_func(Conference a, Conference b) {
|
public static bool equal_func(Conference a, Conference b) {
|
||||||
return a.jid.equals(b.jid);
|
return Jid.equals_func(a.jid, b.jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint hash_func(Conference a) {
|
public static uint hash_func(Conference a) {
|
||||||
|
|
|
@ -6,60 +6,126 @@ public class Jid {
|
||||||
public string? resourcepart;
|
public string? resourcepart;
|
||||||
|
|
||||||
public Jid bare_jid {
|
public Jid bare_jid {
|
||||||
owned get { return is_bare() ? this : new Jid.components(localpart, domainpart, null); }
|
owned get { return is_bare() ? this : new Jid.intern(null, localpart, domainpart, null); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid domain_jid {
|
public Jid domain_jid {
|
||||||
owned get { return is_domain() ? this : new Jid.components(null, domainpart, null); }
|
owned get { return is_domain() ? this : new Jid.intern(domainpart, null, domainpart, null); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private string jid;
|
private string jid;
|
||||||
|
|
||||||
public Jid(string jid) {
|
public Jid(string jid) throws InvalidJidError {
|
||||||
Jid? parsed = Jid.parse(jid);
|
int slash_index = jid.index_of("/");
|
||||||
string? localpart = parsed != null ? (owned) parsed.localpart : null;
|
int at_index = jid.index_of("@");
|
||||||
string domainpart = parsed != null ? (owned) parsed.domainpart : jid;
|
if (at_index > slash_index && slash_index != -1) at_index = -1;
|
||||||
string? resourcepart = parsed != null ? (owned) parsed.resourcepart : null;
|
string resourcepart = slash_index < 0 ? null : jid.slice(slash_index + 1, jid.length);
|
||||||
this.intern(jid, (owned) localpart, (owned) domainpart, (owned) resourcepart);
|
string localpart = at_index < 0 ? null : jid.slice(0, at_index);
|
||||||
|
string domainpart;
|
||||||
|
if (at_index < 0) {
|
||||||
|
if (slash_index < 0) {
|
||||||
|
domainpart = jid;
|
||||||
|
} else {
|
||||||
|
domainpart = jid.slice(0, slash_index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (slash_index < 0) {
|
||||||
|
domainpart = jid.slice(at_index + 1, jid.length);
|
||||||
|
} else {
|
||||||
|
domainpart = jid.slice(at_index + 1, slash_index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Jid.intern(owned string jid, owned string? localpart, owned string domainpart, owned string? resourcepart) {
|
this.components(localpart, domainpart, resourcepart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Jid.intern(owned string? jid, owned string? localpart, owned string domainpart, owned string? resourcepart) {
|
||||||
this.jid = (owned) jid;
|
this.jid = (owned) jid;
|
||||||
this.localpart = (owned) localpart;
|
this.localpart = (owned) localpart;
|
||||||
this.domainpart = (owned) domainpart;
|
this.domainpart = (owned) domainpart;
|
||||||
this.resourcepart = (owned) resourcepart;
|
this.resourcepart = (owned) resourcepart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid.components(owned string? localpart, owned string domainpart, owned string? resourcepart) {
|
public Jid.components(string? localpart, string domainpart, string? resourcepart) throws InvalidJidError {
|
||||||
string jid = domainpart;
|
// TODO verify and normalize all parts
|
||||||
if (localpart != null) {
|
if (domainpart.length == 0) throw new InvalidJidError.EMPTY_DOMAIN("Domain is empty");
|
||||||
jid = @"$localpart@$jid";
|
if (localpart != null && localpart.length == 0) throw new InvalidJidError.EMPTY_LOCAL("Localpart is empty but non-null");
|
||||||
|
if (resourcepart != null && resourcepart.length == 0) throw new InvalidJidError.EMPTY_RESOURCE("Resource is empty but non-null");
|
||||||
|
string domain = domainpart[domainpart.length - 1] == '.' ? domainpart.substring(0, domainpart.length - 1) : domainpart;
|
||||||
|
if (domain.contains("xn--")) {
|
||||||
|
domain = idna_decode(domain);
|
||||||
}
|
}
|
||||||
if (resourcepart != null) {
|
this.localpart = prepare(localpart, ICU.PrepType.RFC3920_NODEPREP);
|
||||||
jid = @"$jid/$resourcepart";
|
this.domainpart = prepare(domain, ICU.PrepType.RFC3491_NAMEPREP);
|
||||||
}
|
this.resourcepart = prepare(resourcepart, ICU.PrepType.RFC3920_RESOURCEPREP);
|
||||||
this.jid = jid;
|
idna_verify(this.domainpart);
|
||||||
this.localpart = (owned) localpart;
|
|
||||||
this.domainpart = (owned) domainpart;
|
|
||||||
this.resourcepart = (owned) resourcepart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Jid? parse(string jid) {
|
private static string idna_decode(string src) throws InvalidJidError {
|
||||||
int slash_index = jid.index_of("/");
|
try {
|
||||||
string resourcepart = slash_index == -1 ? null : jid.slice(slash_index + 1, jid.length);
|
ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
|
||||||
string bare_jid = slash_index == -1 ? jid : jid.slice(0, slash_index);
|
long src16_length = 0;
|
||||||
int at_index = bare_jid.index_of("@");
|
string16 src16 = src.to_utf16(-1, null, out src16_length);
|
||||||
string localpart = at_index == -1 ? null : bare_jid.slice(0, at_index);
|
ICU.Char[] dest16 = new ICU.Char[src16_length];
|
||||||
string domainpart = at_index == -1 ? bare_jid : bare_jid.slice(at_index + 1, bare_jid.length);
|
ICU.ParseError error;
|
||||||
|
long dest16_length = ICU.IDNA.IDNToUnicode(src16, (int32) src16_length, dest16, dest16.length, ICU.IDNAOptions.DEFAULT, out error, ref status);
|
||||||
if (domainpart == "") return null;
|
if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
|
||||||
if (slash_index != -1 && resourcepart == "") return null;
|
throw new InvalidJidError.INVALID_CHAR("Found invalid character");
|
||||||
if (at_index != -1 && localpart == "") return null;
|
} else if (status != ICU.ErrorCode.ZERO_ERROR) {
|
||||||
|
throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
|
||||||
return new Jid.intern(jid, (owned) localpart, (owned) domainpart, (owned) resourcepart);
|
} else if (dest16_length < 0) {
|
||||||
|
throw new InvalidJidError.UNKNOWN("Unknown error");
|
||||||
|
}
|
||||||
|
return ((string16) dest16).to_utf8(dest16_length, null, null);
|
||||||
|
} catch (ConvertError e) {
|
||||||
|
throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid with_resource(string? resourcepart) {
|
private static void idna_verify(string src) throws InvalidJidError {
|
||||||
|
try {
|
||||||
|
ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
|
||||||
|
long src16_length = 0;
|
||||||
|
string16 src16 = src.to_utf16(-1, null, out src16_length);
|
||||||
|
ICU.Char[] dest16 = new ICU.Char[256];
|
||||||
|
ICU.ParseError error;
|
||||||
|
long dest16_length = ICU.IDNA.IDNToASCII(src16, (int32) src16_length, dest16, dest16.length, ICU.IDNAOptions.DEFAULT, out error, ref status);
|
||||||
|
if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
|
||||||
|
throw new InvalidJidError.INVALID_CHAR("Found invalid character");
|
||||||
|
} else if (status != ICU.ErrorCode.ZERO_ERROR) {
|
||||||
|
throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
|
||||||
|
} else if (dest16_length < 0) {
|
||||||
|
throw new InvalidJidError.UNKNOWN("Unknown error");
|
||||||
|
}
|
||||||
|
} catch (ConvertError e) {
|
||||||
|
throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? prepare(string? src, ICU.PrepType type) throws InvalidJidError {
|
||||||
|
if (src == null) return src;
|
||||||
|
try {
|
||||||
|
ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
|
||||||
|
ICU.PrepProfile profile = ICU.PrepProfile.openByType(type, ref status);
|
||||||
|
long src16_length = 0;
|
||||||
|
string16 src16 = src.to_utf16(-1, null, out src16_length);
|
||||||
|
ICU.Char[] dest16 = new ICU.Char[src16_length * 2];
|
||||||
|
ICU.ParseError error;
|
||||||
|
long dest16_length = profile.prepare((ICU.Char*) src16, (int32) src16_length, dest16, dest16.length, ICU.PrepOptions.ALLOW_UNASSIGNED, out error, ref status);
|
||||||
|
if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
|
||||||
|
throw new InvalidJidError.INVALID_CHAR("Found invalid character");
|
||||||
|
} else if (status != ICU.ErrorCode.ZERO_ERROR) {
|
||||||
|
throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
|
||||||
|
} else if (dest16_length < 0) {
|
||||||
|
throw new InvalidJidError.UNKNOWN("Unknown error");
|
||||||
|
}
|
||||||
|
return ((string16) dest16).to_utf8(dest16_length, null, null);
|
||||||
|
} catch (ConvertError e) {
|
||||||
|
throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid with_resource(string? resourcepart) throws InvalidJidError {
|
||||||
return new Jid.components(localpart, domainpart, resourcepart);
|
return new Jid.components(localpart, domainpart, resourcepart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +134,7 @@ public class Jid {
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_bare() {
|
public bool is_bare() {
|
||||||
return localpart != null && resourcepart == null;
|
return resourcepart == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_full() {
|
public bool is_full() {
|
||||||
|
@ -76,6 +142,17 @@ public class Jid {
|
||||||
}
|
}
|
||||||
|
|
||||||
public string to_string() {
|
public string to_string() {
|
||||||
|
if (jid == null) {
|
||||||
|
if (localpart != null && resourcepart != null) {
|
||||||
|
jid = @"$localpart@$domainpart/$resourcepart";
|
||||||
|
} else if (localpart != null) {
|
||||||
|
jid = @"$localpart@$domainpart";
|
||||||
|
} else if (resourcepart != null) {
|
||||||
|
jid = @"$domainpart/$resourcepart";
|
||||||
|
} else {
|
||||||
|
jid = domainpart;
|
||||||
|
}
|
||||||
|
}
|
||||||
return jid;
|
return jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,11 +165,11 @@ public class Jid {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static new bool equals_bare_func(Jid jid1, Jid jid2) {
|
public static new bool equals_bare_func(Jid jid1, Jid jid2) {
|
||||||
return jid1.bare_jid.to_string() == jid2.bare_jid.to_string();
|
return jid1.localpart == jid2.localpart && jid1.domainpart == jid2.domainpart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool equals_func(Jid jid1, Jid jid2) {
|
public static bool equals_func(Jid jid1, Jid jid2) {
|
||||||
return jid1.to_string() == jid2.to_string();
|
return equals_bare_func(jid1, jid2) && jid1.resourcepart == jid2.resourcepart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static new uint hash_bare_func(Jid jid) {
|
public static new uint hash_bare_func(Jid jid) {
|
||||||
|
@ -104,4 +181,12 @@ public class Jid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public errordomain InvalidJidError {
|
||||||
|
EMPTY_DOMAIN,
|
||||||
|
EMPTY_RESOURCE,
|
||||||
|
EMPTY_LOCAL,
|
||||||
|
INVALID_CHAR,
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,15 @@ public class Item {
|
||||||
public StanzaNode stanza_node;
|
public StanzaNode stanza_node;
|
||||||
|
|
||||||
private Jid jid_;
|
private Jid jid_;
|
||||||
public Jid jid {
|
public Jid? jid {
|
||||||
get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(NODE_JID))); }
|
get {
|
||||||
|
try {
|
||||||
|
return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(NODE_JID)));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid Jid in roster entry: %s", e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
set { stanza_node.set_attribute(NODE_JID, value.to_string()); }
|
set { stanza_node.set_attribute(NODE_JID, value.to_string()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,13 @@ public class Stanza : Object {
|
||||||
string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM);
|
string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM);
|
||||||
// "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza
|
// "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza
|
||||||
// is from the user's account on the server." (RFC6120 8.1.2.1)
|
// is from the user's account on the server." (RFC6120 8.1.2.1)
|
||||||
if (from_attribute != null) return from_ = Jid.parse(from_attribute);
|
if (from_attribute != null) {
|
||||||
|
try {
|
||||||
|
return from_ = new Jid(from_attribute);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid from Jid: %s", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (my_jid != null) {
|
if (my_jid != null) {
|
||||||
return my_jid.bare_jid;
|
return my_jid.bare_jid;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +43,12 @@ public class Stanza : Object {
|
||||||
string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO);
|
string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO);
|
||||||
// "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were
|
// "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were
|
||||||
// included with a value of the client's full JID." (RFC6120 8.1.1.1)
|
// included with a value of the client's full JID." (RFC6120 8.1.1.1)
|
||||||
return to_attribute == null ? my_jid : to_ = Jid.parse(to_attribute);
|
try {
|
||||||
|
return to_attribute == null ? my_jid : to_ = new Jid(to_attribute);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring invalid to Jid: %s", e.message);
|
||||||
|
}
|
||||||
|
return my_jid;
|
||||||
}
|
}
|
||||||
set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); }
|
set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,13 @@ public class ItemsResult {
|
||||||
owned get {
|
owned get {
|
||||||
ArrayList<Item> ret = new ArrayList<Item>();
|
ArrayList<Item> ret = new ArrayList<Item>();
|
||||||
foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_ITEMS).get_subnodes("item", NS_URI_ITEMS)) {
|
foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_ITEMS).get_subnodes("item", NS_URI_ITEMS)) {
|
||||||
ret.add(new Item(Jid.parse(feature_node.get_attribute("jid", NS_URI_ITEMS)),
|
try {
|
||||||
|
ret.add(new Item(new Jid(feature_node.get_attribute("jid", NS_URI_ITEMS)),
|
||||||
feature_node.get_attribute("name", NS_URI_ITEMS),
|
feature_node.get_attribute("name", NS_URI_ITEMS),
|
||||||
feature_node.get_attribute("node", NS_URI_ITEMS)));
|
feature_node.get_attribute("node", NS_URI_ITEMS)));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring service at invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,10 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since) {
|
public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since) {
|
||||||
|
try {
|
||||||
Presence.Stanza presence = new Presence.Stanza();
|
Presence.Stanza presence = new Presence.Stanza();
|
||||||
presence.to = bare_jid.with_resource(nick);
|
presence.to = bare_jid.with_resource(nick);
|
||||||
|
|
||||||
StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns();
|
StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns();
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password)));
|
x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password)));
|
||||||
|
@ -109,14 +111,21 @@ public class Module : XmppStreamModule {
|
||||||
} catch (Gee.FutureError e) {
|
} catch (Gee.FutureError e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
return new JoinResult() { muc_error = MucEnterError.NICK_CONFLICT };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exit(XmppStream stream, Jid jid) {
|
public void exit(XmppStream stream, Jid jid) {
|
||||||
|
try {
|
||||||
string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid);
|
string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid);
|
||||||
Presence.Stanza presence = new Presence.Stanza();
|
Presence.Stanza presence = new Presence.Stanza();
|
||||||
presence.to = jid.with_resource(nick);
|
presence.to = jid.with_resource(nick);
|
||||||
presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE;
|
presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE;
|
||||||
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
|
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Tried to leave room with invalid nick: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void change_subject(XmppStream stream, Jid jid, string subject) {
|
public void change_subject(XmppStream stream, Jid jid, string subject) {
|
||||||
|
@ -128,9 +137,14 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void change_nick(XmppStream stream, Jid jid, string new_nick) {
|
public void change_nick(XmppStream stream, Jid jid, string new_nick) {
|
||||||
|
// TODO: Return if successful
|
||||||
|
try {
|
||||||
Presence.Stanza presence = new Presence.Stanza();
|
Presence.Stanza presence = new Presence.Stanza();
|
||||||
presence.to = jid.with_resource(new_nick);
|
presence.to = jid.with_resource(new_nick);
|
||||||
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
|
stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Tried to change nick to invalid nick: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invite(XmppStream stream, Jid to_muc, Jid jid) {
|
public void invite(XmppStream stream, Jid to_muc, Jid jid) {
|
||||||
|
@ -148,6 +162,7 @@ public class Module : XmppStreamModule {
|
||||||
|
|
||||||
/* XEP 0046: "A user cannot be kicked by a moderator with a lower affiliation." (XEP 0045 8.2) */
|
/* XEP 0046: "A user cannot be kicked by a moderator with a lower affiliation." (XEP 0045 8.2) */
|
||||||
public bool kick_possible(XmppStream stream, Jid occupant) {
|
public bool kick_possible(XmppStream stream, Jid occupant) {
|
||||||
|
try {
|
||||||
Jid muc_jid = occupant.bare_jid;
|
Jid muc_jid = occupant.bare_jid;
|
||||||
Flag flag = stream.get_flag(Flag.IDENTITY);
|
Flag flag = stream.get_flag(Flag.IDENTITY);
|
||||||
string own_nick = flag.get_muc_nick(muc_jid);
|
string own_nick = flag.get_muc_nick(muc_jid);
|
||||||
|
@ -162,6 +177,10 @@ public class Module : XmppStreamModule {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Tried to kick with invalid nick: %s", e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void change_role(XmppStream stream, Jid jid, string nick, string new_role) {
|
public void change_role(XmppStream stream, Jid jid, string nick, string new_role) {
|
||||||
|
@ -314,12 +333,16 @@ public class Module : XmppStreamModule {
|
||||||
}
|
}
|
||||||
string? jid_ = x_node.get_deep_attribute("item", "jid");
|
string? jid_ = x_node.get_deep_attribute("item", "jid");
|
||||||
if (jid_ != null) {
|
if (jid_ != null) {
|
||||||
Jid? jid = Jid.parse(jid_);
|
try {
|
||||||
|
Jid jid = new Jid(jid_);
|
||||||
flag.set_real_jid(presence.from, jid);
|
flag.set_real_jid(presence.from, jid);
|
||||||
if (affiliation != null) {
|
if (affiliation != null) {
|
||||||
stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation);
|
stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation);
|
||||||
}
|
}
|
||||||
received_occupant_jid(stream, presence.from, jid);
|
received_occupant_jid(stream, presence.from, jid);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received invalid occupant jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
string? role_str = x_node.get_deep_attribute("item", "role");
|
string? role_str = x_node.get_deep_attribute("item", "role");
|
||||||
if (role_str != null) {
|
if (role_str != null) {
|
||||||
|
@ -414,12 +437,17 @@ public class Module : XmppStreamModule {
|
||||||
Gee.List<StanzaNode> item_nodes = query_node.get_subnodes("item", NS_URI_ADMIN);
|
Gee.List<StanzaNode> item_nodes = query_node.get_subnodes("item", NS_URI_ADMIN);
|
||||||
Gee.List<Jid> ret_jids = new ArrayList<Jid>(Jid.equals_func);
|
Gee.List<Jid> ret_jids = new ArrayList<Jid>(Jid.equals_func);
|
||||||
foreach (StanzaNode item in item_nodes) {
|
foreach (StanzaNode item in item_nodes) {
|
||||||
Jid? jid_ = Jid.parse(item.get_attribute("jid"));
|
string jid__ = item.get_attribute("jid");
|
||||||
string? affiliation_ = item.get_attribute("affiliation");
|
string? affiliation_ = item.get_attribute("affiliation");
|
||||||
if (jid_ != null && affiliation_ != null) {
|
if (jid__ != null && affiliation_ != null) {
|
||||||
|
try {
|
||||||
|
Jid jid_ = new Jid(jid__);
|
||||||
stream.get_flag(Flag.IDENTITY).set_offline_member(iq.from, jid_, parse_affiliation(affiliation_));
|
stream.get_flag(Flag.IDENTITY).set_offline_member(iq.from, jid_, parse_affiliation(affiliation_));
|
||||||
ret_jids.add(jid_);
|
ret_jids.add(jid_);
|
||||||
received_occupant_jid(stream, iq.from, jid_);
|
received_occupant_jid(stream, iq.from, jid_);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received invalid occupant jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (listener != null) listener(stream, ret_jids);
|
if (listener != null) listener(stream, ret_jids);
|
||||||
|
@ -489,13 +517,19 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
|
||||||
StanzaNode? password_node = x_node.get_subnode("password", NS_URI_USER);
|
StanzaNode? password_node = x_node.get_subnode("password", NS_URI_USER);
|
||||||
if (password_node != null) password = password_node.get_string_content();
|
if (password_node != null) password = password_node.get_string_content();
|
||||||
if (invite_node != null) {
|
if (invite_node != null) {
|
||||||
string? from_jid = invite_node.get_attribute("from");
|
Jid? from_jid = null;
|
||||||
|
try {
|
||||||
|
string from = invite_node.get_attribute("from");
|
||||||
|
if (from != null) from_jid = new Jid(from);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Received invite from invalid jid: %s", e.message);
|
||||||
|
}
|
||||||
if (from_jid != null) {
|
if (from_jid != null) {
|
||||||
StanzaNode? reason_node = invite_node.get_subnode("reason", NS_URI_USER);
|
StanzaNode? reason_node = invite_node.get_subnode("reason", NS_URI_USER);
|
||||||
string? reason = null;
|
string? reason = null;
|
||||||
if (reason_node != null) reason = reason_node.get_string_content();
|
if (reason_node != null) reason = reason_node.get_string_content();
|
||||||
bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(message) != null; // TODO
|
bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(message) != null; // TODO
|
||||||
if (!is_mam_message) outer.invite_received(stream, message.from, new Jid(from_jid), password, reason);
|
if (!is_mam_message) outer.invite_received(stream, message.from, from_jid, password, reason);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,14 @@ public class Bookmarks1Conference : Conference {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Jid jid_;
|
private Jid jid_;
|
||||||
public override Jid jid {
|
public override Jid? jid {
|
||||||
get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(ATTRIBUTE_JID))); }
|
get {
|
||||||
|
try {
|
||||||
|
return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(ATTRIBUTE_JID)));
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
set { stanza_node.set_attribute(ATTRIBUTE_JID, value.to_string()); }
|
set { stanza_node.set_attribute(ATTRIBUTE_JID, value.to_string()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,11 @@ public class Module : XmppStreamModule, Iq.Handler {
|
||||||
}
|
}
|
||||||
string? host = stream_host.get_attribute("host");
|
string? host = stream_host.get_attribute("host");
|
||||||
string? jid_str = stream_host.get_attribute("jid");
|
string? jid_str = stream_host.get_attribute("jid");
|
||||||
Jid? jid = jid_str != null ? Jid.parse(jid_str) : null;
|
Jid? jid = null;
|
||||||
|
try {
|
||||||
|
jid = jid_str != null ? new Jid(jid_str) : null;
|
||||||
|
} catch (InvalidJidError ignored) {
|
||||||
|
}
|
||||||
int port = stream_host.get_attribute_int("port");
|
int port = stream_host.get_attribute_int("port");
|
||||||
if (host == null || jid == null || port <= 0 || port > 65535) {
|
if (host == null || jid == null || port <= 0 || port > 65535) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -572,13 +572,15 @@ public class Session {
|
||||||
}
|
}
|
||||||
void handle_session_accept(XmppStream stream, ContentNode content, StanzaNode jingle, Iq.Stanza iq) throws IqError {
|
void handle_session_accept(XmppStream stream, ContentNode content, StanzaNode jingle, Iq.Stanza iq) throws IqError {
|
||||||
string? responder_str = jingle.get_attribute("responder");
|
string? responder_str = jingle.get_attribute("responder");
|
||||||
Jid responder;
|
Jid responder = iq.from;
|
||||||
if (responder_str != null) {
|
if (responder_str != null) {
|
||||||
responder = Jid.parse(responder_str) ?? iq.from;
|
try {
|
||||||
} else {
|
responder = new Jid(responder_str);
|
||||||
responder = iq.from; // TODO(hrxi): and above, can we assume iq.from != null
|
} catch (InvalidJidError e) {
|
||||||
// TODO(hrxi): more sanity checking, perhaps replace who we're talking to
|
warning("Received invalid session accept: %s", e.message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// TODO(hrxi): more sanity checking, perhaps replace who we're talking to
|
||||||
if (!responder.is_full()) {
|
if (!responder.is_full()) {
|
||||||
throw new IqError.BAD_REQUEST("invalid responder JID");
|
throw new IqError.BAD_REQUEST("invalid responder JID");
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,11 @@ public class Candidate : Socks5Bytestreams.Proxy {
|
||||||
string? cid = candidate.get_attribute("cid");
|
string? cid = candidate.get_attribute("cid");
|
||||||
string? host = candidate.get_attribute("host");
|
string? host = candidate.get_attribute("host");
|
||||||
string? jid_str = candidate.get_attribute("jid");
|
string? jid_str = candidate.get_attribute("jid");
|
||||||
Jid? jid = jid_str != null ? Jid.parse(jid_str) : null;
|
Jid? jid = null;
|
||||||
|
try {
|
||||||
|
jid = new Jid(jid_str);
|
||||||
|
} catch (InvalidJidError ignored) {
|
||||||
|
}
|
||||||
int port = candidate.get_attribute("port") != null ? candidate.get_attribute_int("port") : 1080;
|
int port = candidate.get_attribute("port") != null ? candidate.get_attribute_int("port") : 1080;
|
||||||
int priority = candidate.get_attribute_int("priority");
|
int priority = candidate.get_attribute_int("priority");
|
||||||
string? type_str = candidate.get_attribute("type");
|
string? type_str = candidate.get_attribute("type");
|
||||||
|
|
|
@ -66,19 +66,28 @@ public class Module : BookmarksProvider, XmppStreamModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
|
||||||
Jid jid_parsed = Jid.parse(id);
|
try {
|
||||||
|
Jid jid_parsed = new Jid(id);
|
||||||
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
Flag? flag = stream.get_flag(Flag.IDENTITY);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
flag.conferences.unset(jid_parsed);
|
flag.conferences.unset(jid_parsed);
|
||||||
}
|
}
|
||||||
conference_removed(stream, jid_parsed);
|
conference_removed(stream, jid_parsed);
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring conference bookmark update with invalid Jid: %s", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Conference? parse_item_node(StanzaNode conference_node, string id) {
|
private Conference? parse_item_node(StanzaNode conference_node, string id) {
|
||||||
Conference conference = new Conference();
|
Conference conference = new Conference();
|
||||||
Jid? jid_parsed = Jid.parse(id);
|
try {
|
||||||
if (jid_parsed == null || jid_parsed.resourcepart != null) return null;
|
Jid jid_parsed = new Jid(id);
|
||||||
|
if (jid_parsed.resourcepart != null) return null;
|
||||||
conference.jid = jid_parsed;
|
conference.jid = jid_parsed;
|
||||||
|
} catch (InvalidJidError e) {
|
||||||
|
warning("Ignoring conference bookmark update with invalid Jid: %s", e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (conference_node.name != "conference" || conference_node.ns_uri != NS_URI) return null;
|
if (conference_node.name != "conference" || conference_node.ns_uri != NS_URI) return null;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ int main(string[] args) {
|
||||||
GLib.Test.set_nonfatal_assertions();
|
GLib.Test.set_nonfatal_assertions();
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
||||||
|
TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
|
||||||
return GLib.Test.run();
|
return GLib.Test.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +75,13 @@ bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
|
||||||
return fail_if_not(!nullcheck && left == right, @"$(reason + ": " ?? "")$left != $right");
|
return fail_if_not(!nullcheck && left == right, @"$(reason + ": " ?? "")$left != $right");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fail_if_eq_str(string? left, string? right, string? reason = null) {
|
||||||
|
bool nullcheck = (left == null && right != null) || (left != null && right == null);
|
||||||
|
if (left == null) left = "(null)";
|
||||||
|
if (right == null) right = "(null)";
|
||||||
|
return fail_if(!nullcheck && left == right, @"$(reason + ": " ?? "")$left == $right");
|
||||||
|
}
|
||||||
|
|
||||||
bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) {
|
bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) {
|
||||||
if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true;
|
if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true;
|
||||||
return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason);
|
return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason);
|
||||||
|
|
93
xmpp-vala/tests/jid.vala
Normal file
93
xmpp-vala/tests/jid.vala
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
namespace Xmpp.Test {
|
||||||
|
|
||||||
|
class JidTest : Gee.TestCase {
|
||||||
|
public JidTest() {
|
||||||
|
base("Jid");
|
||||||
|
|
||||||
|
add_test("jid_valid_domain_only", () => { test_jid_valid("example.com"); });
|
||||||
|
add_test("jid_valid_bare", () => { test_jid_valid("test@example.com"); });
|
||||||
|
add_test("jid_valid_domain_with_resource", () => { test_jid_valid("example.com/test"); });
|
||||||
|
add_test("jid_valid_full", () => { test_jid_valid("test@example.com/test"); });
|
||||||
|
|
||||||
|
// Should those actually be valid?
|
||||||
|
add_test("jid_valid_emoji_local", () => { test_jid_valid("😅@example.com"); });
|
||||||
|
add_test("jid_valid_emoji_resource", () => { test_jid_valid("test@example.com/😅"); });
|
||||||
|
|
||||||
|
add_test("jid_invalid_emoji_domain", () => { test_jid_invalid("test@😅.com"); });
|
||||||
|
add_test("jid_invalid_bidi_local", () => { test_jid_invalid("test@example.com"); });
|
||||||
|
add_test("jid_invalid_bidi_resource", () => { test_jid_invalid("test@example.com/test"); });
|
||||||
|
add_test("jid_invalid_bidi_domain", () => { test_jid_invalid("test@example.com"); });
|
||||||
|
add_test("jid_invalid_overlong_idn", () => { test_jid_invalid("test@ççççççççççççççççççççççççççççççççççççççççççççççççççççççççççççç.com"); });
|
||||||
|
|
||||||
|
add_test("jid_equal_end_domain", () => { test_jids_equal("test@example.com", "test@example.com."); });
|
||||||
|
add_test("jid_equal_case_domain", () => { test_jids_equal("test@example.com", "test@eXample.com"); });
|
||||||
|
add_test("jid_equal_norm_domain", () => { test_jids_equal("test@garçon.com", "test@garçon.com"); });
|
||||||
|
add_test("jid_equal_puny_domain", () => { test_jids_equal("test@garçon.com", "test@xn--garon-0ra.com"); });
|
||||||
|
add_test("jid_equal_case_local", () => { test_jids_equal("test@example.com", "tEst@example.com"); });
|
||||||
|
add_test("jid_equal_norm_local", () => { test_jids_equal("garçon@example.com", "garçon@example.com"); });
|
||||||
|
add_test("jid_equal_norm_resource", () => { test_jids_equal("test@example.com/garçon", "test@example.com/garçon"); });
|
||||||
|
|
||||||
|
add_test("jid_non_equal_case_resource", () => { test_jids_unequal("example.com/test", "example.com/tEst"); });
|
||||||
|
|
||||||
|
add_test("jid_to_string_end_domain", () => { test_jid_to_string("test@example.com.", "test@example.com"); });
|
||||||
|
add_test("jid_to_string_case_domain", () => { test_jid_to_string("test@eXample.com", "test@example.com"); });
|
||||||
|
add_test("jid_to_string_norm_domain", () => { test_jid_to_string("test@garçon.com", "test@garçon.com"); });
|
||||||
|
add_test("jid_to_string_puny_domain", () => { test_jid_to_string("test@xn--garon-0ra.com", "test@garçon.com"); });
|
||||||
|
add_test("jid_to_string_case_local", () => { test_jid_to_string("tEst@example.com", "test@example.com"); });
|
||||||
|
add_test("jid_to_string_norm_local", () => { test_jid_to_string("garçon@example.com", "garçon@example.com"); });
|
||||||
|
add_test("jid_to_string_case_resource", () => { test_jid_to_string("example.com/tEst", "example.com/tEst"); });
|
||||||
|
add_test("jid_to_string_norm_resource", () => { test_jid_to_string("test@example.com/garçon", "test@example.com/garçon"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test_jid_valid(string jid) {
|
||||||
|
try {
|
||||||
|
new Jid(jid);
|
||||||
|
} catch (Error e) {
|
||||||
|
fail_if_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test_jid_invalid(string jid) {
|
||||||
|
try {
|
||||||
|
new Jid(jid);
|
||||||
|
fail_if_reached();
|
||||||
|
} catch (Error e) {
|
||||||
|
// try {
|
||||||
|
// fail_if_not_eq_str(Jid.parse(jid).to_string(), jid);
|
||||||
|
// } catch (Error e) {
|
||||||
|
// fail_if_reached();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test_jids_equal(string jid1, string jid2) {
|
||||||
|
try {
|
||||||
|
var t1 = new Jid(jid1);
|
||||||
|
var t2 = new Jid(jid2);
|
||||||
|
fail_if_not_eq_str(t1.to_string(), t2.to_string());
|
||||||
|
} catch (Error e) {
|
||||||
|
fail_if_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test_jid_to_string(string jid1, string jid2) {
|
||||||
|
try {
|
||||||
|
var t1 = new Jid(jid1);
|
||||||
|
fail_if_not_eq_str(t1.to_string(), jid2);
|
||||||
|
} catch (Error e) {
|
||||||
|
fail_if_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test_jids_unequal(string jid1, string jid2) {
|
||||||
|
try {
|
||||||
|
var t1 = new Jid(jid1);
|
||||||
|
var t2 = new Jid(jid2);
|
||||||
|
fail_if_eq_str(t1.to_string(), t2.to_string());
|
||||||
|
} catch (Error e) {
|
||||||
|
fail_if_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
xmpp-vala/vapi/icu-uc.vapi
Normal file
56
xmpp-vala/vapi/icu-uc.vapi
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
namespace ICU {
|
||||||
|
|
||||||
|
[CCode (cname = "UChar")]
|
||||||
|
[IntegerType (rank = 5, min = 0, max = 65535)]
|
||||||
|
struct Char {}
|
||||||
|
|
||||||
|
[CCode (cname = "UErrorCode", cprefix = "U_", cheader_filename = "unicode/utypes.h")]
|
||||||
|
enum ErrorCode {
|
||||||
|
ZERO_ERROR,
|
||||||
|
INVALID_CHAR_FOUND,
|
||||||
|
INDEX_OUTOFBOUNDS_ERROR,
|
||||||
|
BUFFER_OVERFLOW_ERROR,
|
||||||
|
UNASSIGNED_CODE_POINT_FOUND,
|
||||||
|
IDNA_STD3_ASCII_RULES_ERROR
|
||||||
|
;
|
||||||
|
[CCode (cname = "u_errorName")]
|
||||||
|
public unowned string errorName();
|
||||||
|
}
|
||||||
|
|
||||||
|
[CCode (cname = "UErrorCode", cprefix = "U_", cheader_filename = "unicode/parseerr.h")]
|
||||||
|
struct ParseError {}
|
||||||
|
|
||||||
|
[CCode (cname = "UStringPrepProfile", cprefix = "usprep_", free_function = "usprep_close", cheader_filename = "unicode/usprep.h")]
|
||||||
|
[Compact]
|
||||||
|
class PrepProfile {
|
||||||
|
public static PrepProfile open(string path, string file_name, ref ErrorCode status);
|
||||||
|
public static PrepProfile openByType(PrepType type, ref ErrorCode status);
|
||||||
|
public int32 prepare(Char* src, int32 src_length, Char* dest, int32 dest_capacity, PrepOptions options, out ParseError parse_error, ref ErrorCode status);
|
||||||
|
}
|
||||||
|
[CCode (cname = "UStringPrepProfileType", cprefix = "USPREP_")]
|
||||||
|
enum PrepType {
|
||||||
|
RFC3491_NAMEPREP,
|
||||||
|
RFC3920_NODEPREP,
|
||||||
|
RFC3920_RESOURCEPREP
|
||||||
|
}
|
||||||
|
[CCode (cname = "int32_t", cprefix = "USPREP_")]
|
||||||
|
enum PrepOptions {
|
||||||
|
DEFAULT,
|
||||||
|
ALLOW_UNASSIGNED
|
||||||
|
}
|
||||||
|
|
||||||
|
[CCode (cname = "UIDNA", cprefix = "uidna_", free_function = "uidna_close", cheader_filename = "unicode/uidna.h")]
|
||||||
|
[Compact]
|
||||||
|
class IDNA {
|
||||||
|
public static int32 IDNToUnicode(Char* src, int32 src_length, Char* dest, int32 dest_capacity, IDNAOptions options, out ParseError parse_error, ref ErrorCode status);
|
||||||
|
public static int32 IDNToASCII(Char* src, int32 src_length, Char* dest, int32 dest_capacity, IDNAOptions options, out ParseError parse_error, ref ErrorCode status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CCode (cname = "uint32_t", cprefix = "UIDNA_")]
|
||||||
|
enum IDNAOptions {
|
||||||
|
DEFAULT,
|
||||||
|
ALLOW_UNASSIGNED,
|
||||||
|
USE_STD3_RULES
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue