SHA256
Сервер: уточнить правила имён каналов и поиск треда
This commit is contained in:
@@ -8,6 +8,10 @@ public final class ChannelNameRules {
|
|||||||
private static final int MAX_DISPLAY_NAME_LENGTH = 32;
|
private static final int MAX_DISPLAY_NAME_LENGTH = 32;
|
||||||
private static final Pattern DISPLAY_ALLOWED_PATTERN =
|
private static final Pattern DISPLAY_ALLOWED_PATTERN =
|
||||||
Pattern.compile("^[A-Za-z0-9_-]+$");
|
Pattern.compile("^[A-Za-z0-9_-]+$");
|
||||||
|
private static final Pattern PUBLIC_CHANNEL_ALLOWED_PATTERN =
|
||||||
|
Pattern.compile("^[A-Za-z0-9_-]+$");
|
||||||
|
private static final Pattern DIGITS_ONLY_PATTERN =
|
||||||
|
Pattern.compile("^[0-9]+$");
|
||||||
|
|
||||||
private ChannelNameRules() {}
|
private ChannelNameRules() {}
|
||||||
|
|
||||||
@@ -34,6 +38,17 @@ public final class ChannelNameRules {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String requireValidPublicDisplayNameForCreate(String rawName) {
|
||||||
|
String normalized = requireValidDisplayNameForCreate(rawName);
|
||||||
|
if (!PUBLIC_CHANNEL_ALLOWED_PATTERN.matcher(normalized).matches()) {
|
||||||
|
throw new IllegalArgumentException("channelName contains unsupported characters");
|
||||||
|
}
|
||||||
|
if (DIGITS_ONLY_PATTERN.matcher(normalized).matches()) {
|
||||||
|
throw new IllegalArgumentException("channelName must not contain only digits");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
public static String toCanonicalSlug(String rawName) {
|
public static String toCanonicalSlug(String rawName) {
|
||||||
String normalized = normalizeDisplayName(rawName);
|
String normalized = normalizeDisplayName(rawName);
|
||||||
if (normalized.isEmpty()) {
|
if (normalized.isEmpty()) {
|
||||||
|
|||||||
+5
-4
@@ -279,18 +279,19 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
|||||||
ChannelNameStateEntry channelNameStateEntry = null;
|
ChannelNameStateEntry channelNameStateEntry = null;
|
||||||
Chat200CreateSeed chat200CreateSeed = null;
|
Chat200CreateSeed chat200CreateSeed = null;
|
||||||
if (block.body instanceof CreateChannelBody createChannelBody) {
|
if (block.body instanceof CreateChannelBody createChannelBody) {
|
||||||
|
int channelTypeCode = Short.toUnsignedInt(createChannelBody.channelTypeCode);
|
||||||
|
int channelTypeVersion = Short.toUnsignedInt(createChannelBody.channelTypeVersion);
|
||||||
final String normalizedName;
|
final String normalizedName;
|
||||||
final String slug;
|
final String slug;
|
||||||
try {
|
try {
|
||||||
normalizedName = ChannelNameRules.requireValidDisplayNameForCreate(createChannelBody.channelName);
|
normalizedName = channelTypeCode == (CreateChannelBody.CHANNEL_TYPE_PERSONAL & 0xFFFF)
|
||||||
|
? ChannelNameRules.requireValidDisplayNameForCreate(createChannelBody.channelName)
|
||||||
|
: ChannelNameRules.requireValidPublicDisplayNameForCreate(createChannelBody.channelName);
|
||||||
slug = ChannelNameRules.toCanonicalSlug(normalizedName);
|
slug = ChannelNameRules.toCanonicalSlug(normalizedName);
|
||||||
} catch (IllegalArgumentException badName) {
|
} catch (IllegalArgumentException badName) {
|
||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_channel_name", serverLastNum, serverLastHashHex);
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_channel_name", serverLastNum, serverLastHashHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int channelTypeCode = Short.toUnsignedInt(createChannelBody.channelTypeCode);
|
|
||||||
int channelTypeVersion = Short.toUnsignedInt(createChannelBody.channelTypeVersion);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (channelNameStateDAO.existsByOwnerTypeAndSlug(blockchainName, channelTypeCode, slug)) {
|
if (channelNameStateDAO.existsByOwnerTypeAndSlug(blockchainName, channelTypeCode, slug)) {
|
||||||
return new AddBlockResult(409, "channel_name_already_exists", serverLastNum, serverLastHashHex);
|
return new AddBlockResult(409, "channel_name_already_exists", serverLastNum, serverLastHashHex);
|
||||||
|
|||||||
-20
@@ -84,11 +84,6 @@ public class Net_GetMessageThread_Handler implements JsonMessageHandler {
|
|||||||
PostRow byNumber = findByNumber(c, blockchainName, blockNumber);
|
PostRow byNumber = findByNumber(c, blockchainName, blockNumber);
|
||||||
if (byNumber != null) return byNumber;
|
if (byNumber != null) return byNumber;
|
||||||
|
|
||||||
if (blockHash != null) {
|
|
||||||
PostRow byHash = findByHash(c, blockHash);
|
|
||||||
if (byHash != null) return byHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,21 +156,6 @@ public class Net_GetMessageThread_Handler implements JsonMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PostRow findByHash(Connection c, byte[] blockHash) throws Exception {
|
|
||||||
String sql = """
|
|
||||||
SELECT login,bch_name,block_number,block_hash,block_bytes,to_bch_name,to_block_number,to_block_hash,line_code,msg_sub_type,this_line_number
|
|
||||||
FROM blocks
|
|
||||||
WHERE block_hash=?
|
|
||||||
LIMIT 1
|
|
||||||
""";
|
|
||||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
|
||||||
ps.setBytes(1, blockHash);
|
|
||||||
try (ResultSet rs = ps.executeQuery()) {
|
|
||||||
return rs.next() ? mapRow(rs) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] parseOptionalHash(String hex) {
|
private byte[] parseOptionalHash(String hex) {
|
||||||
String value = String.valueOf(hex == null ? "" : hex).trim();
|
String value = String.valueOf(hex == null ? "" : hex).trim();
|
||||||
if (value.isEmpty()) return null;
|
if (value.isEmpty()) return null;
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.354
|
client.version=1.2.354
|
||||||
server.version=1.2.328
|
server.version=1.2.329
|
||||||
|
|||||||
@@ -45,12 +45,13 @@ export function render({ navigate }) {
|
|||||||
form.className = 'card stack';
|
form.className = 'card stack';
|
||||||
form.innerHTML = `
|
form.innerHTML = `
|
||||||
<strong class="channel-head-title">Создание канала</strong>
|
<strong class="channel-head-title">Создание канала</strong>
|
||||||
<p class="channel-head-meta">Можно использовать только латиницу, цифры, _ и -.</p>
|
<p class="channel-head-meta">Разрешены только латинские буквы, цифры, _ и -.</p>
|
||||||
<p class="channel-head-meta">Длина названия: от 1 до 32 символов.</p>
|
<p class="channel-head-meta">Длина названия: от 3 до 32 символов. Название не должно состоять только из цифр.</p>
|
||||||
|
<p class="channel-head-meta">Можно использовать большие и маленькие буквы, но уникальность проверяется без учёта регистра.</p>
|
||||||
<div class="meta-muted">Тип канала фиксирован: публичный (1).</div>
|
<div class="meta-muted">Тип канала фиксирован: публичный (1).</div>
|
||||||
|
|
||||||
<label for="channel-name">Название канала</label>
|
<label for="channel-name">Название канала</label>
|
||||||
<input id="channel-name" class="input" maxlength="32" placeholder="Например: my_channel-1" required />
|
<input id="channel-name" class="input" maxlength="32" placeholder="Например: My-Channel_1" required />
|
||||||
<div id="channel-name-error" class="meta-muted inline-error"></div>
|
<div id="channel-name-error" class="meta-muted inline-error"></div>
|
||||||
|
|
||||||
<label for="channel-description">Описание канала (необязательно)</label>
|
<label for="channel-description">Описание канала (необязательно)</label>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const MIN_LEN = 1;
|
const MIN_LEN = 3;
|
||||||
const MAX_LEN = 32;
|
const MAX_LEN = 32;
|
||||||
const ALLOWED_CHARS_RE = /^[A-Za-z0-9_-]+$/;
|
const ALLOWED_CHARS_RE = /^[A-Za-z0-9_-]+$/;
|
||||||
|
const DIGITS_ONLY_RE = /^[0-9]+$/;
|
||||||
|
|
||||||
export function normalizeChannelDisplayName(value) {
|
export function normalizeChannelDisplayName(value) {
|
||||||
if (value == null) return '';
|
if (value == null) return '';
|
||||||
@@ -37,8 +38,8 @@ export function validateChannelDisplayName(value) {
|
|||||||
if (!ALLOWED_CHARS_RE.test(normalized)) {
|
if (!ALLOWED_CHARS_RE.test(normalized)) {
|
||||||
return { ok: false, code: 'bad_chars', normalized, slug: '' };
|
return { ok: false, code: 'bad_chars', normalized, slug: '' };
|
||||||
}
|
}
|
||||||
if (normalized === '0') {
|
if (DIGITS_ONLY_RE.test(normalized)) {
|
||||||
return { ok: false, code: 'reserved', normalized, slug: '' };
|
return { ok: false, code: 'digits_only', normalized, slug: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const slug = toCanonicalChannelSlug(normalized);
|
const slug = toCanonicalChannelSlug(normalized);
|
||||||
@@ -54,13 +55,13 @@ export function channelNameErrorText(code) {
|
|||||||
case 'blank':
|
case 'blank':
|
||||||
return 'Введите название канала.';
|
return 'Введите название канала.';
|
||||||
case 'too_short':
|
case 'too_short':
|
||||||
return 'Название слишком короткое: минимум 1 символ.';
|
return 'Название слишком короткое: минимум 3 символа.';
|
||||||
case 'too_long':
|
case 'too_long':
|
||||||
return 'Название слишком длинное: максимум 32 символа.';
|
return 'Название слишком длинное: максимум 32 символа.';
|
||||||
case 'bad_chars':
|
case 'bad_chars':
|
||||||
return 'Разрешены только латиница, цифры, _ и -.';
|
return 'Разрешены только латиница, цифры, _ и -.';
|
||||||
case 'reserved':
|
case 'digits_only':
|
||||||
return 'Название "0" зарезервировано.';
|
return 'Название канала не должно состоять только из цифр.';
|
||||||
default:
|
default:
|
||||||
return 'Некорректное название канала.';
|
return 'Некорректное название канала.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,11 +86,12 @@ export function toUserMessage(error, fallback = 'Действие не выпо
|
|||||||
code === 'BAD_CHANNEL_NAME' ||
|
code === 'BAD_CHANNEL_NAME' ||
|
||||||
text.includes('channel name must match') ||
|
text.includes('channel name must match') ||
|
||||||
text.includes('channelname contains unsupported') ||
|
text.includes('channelname contains unsupported') ||
|
||||||
|
text.includes('channelname must not contain only digits') ||
|
||||||
text.includes('channelname length must be 3..32') ||
|
text.includes('channelname length must be 3..32') ||
|
||||||
text.includes('channelname length must be 1..32') ||
|
text.includes('channelname length must be 1..32') ||
|
||||||
text.includes('bad_channel_name')
|
text.includes('bad_channel_name')
|
||||||
) {
|
) {
|
||||||
return 'Некорректное название канала. Разрешены кириллица, латиница, цифры, пробел, _ и - (1..32 символа).';
|
return 'Некорректное название канала. Разрешены только латинские буквы, цифры, _ и -. Длина: 3..32. Название не должно состоять только из цифр.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.includes('channel name is required') || text.includes('введите имя канала')) {
|
if (text.includes('channel name is required') || text.includes('введите имя канала')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user