mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-03 12:44:33 +02:00
tcp_transport: renamed transport related header files to esp_ prefixed to avoid collisions
tcp_transport component used public header files such as 'transport.h', etc. which are too generic and might collide with user or user libraries headers This change closes #2417
This commit is contained in:
@@ -19,17 +19,16 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
#include "http_header.h"
|
#include "http_header.h"
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
#include "transport_tcp.h"
|
#include "esp_transport_tcp.h"
|
||||||
#include "http_utils.h"
|
#include "http_utils.h"
|
||||||
#include "http_auth.h"
|
#include "http_auth.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "transport.h"
|
|
||||||
#include "esp_http_client.h"
|
#include "esp_http_client.h"
|
||||||
#include "errno.h"
|
#include "errno.h"
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
|
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
|
||||||
#include "transport_ssl.h"
|
#include "esp_transport_ssl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char *TAG = "HTTP_CLIENT";
|
static const char *TAG = "HTTP_CLIENT";
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
#ifndef _HTTP_UTILS_H_
|
#ifndef _HTTP_UTILS_H_
|
||||||
#define _HTTP_UTILS_H_
|
#define _HTTP_UTILS_H_
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include "transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
/**
|
/**
|
||||||
* @brief Assign new_str to *str pointer, and realloc *str if it not NULL
|
* @brief Assign new_str to *str pointer, and realloc *str if it not NULL
|
||||||
*
|
*
|
||||||
|
@@ -3,17 +3,8 @@ set(COMPONENT_PRIV_INCLUDEDIRS "esp-mqtt/lib/include")
|
|||||||
set(COMPONENT_SRCS "esp-mqtt/mqtt_client.c"
|
set(COMPONENT_SRCS "esp-mqtt/mqtt_client.c"
|
||||||
"esp-mqtt/lib/mqtt_msg.c"
|
"esp-mqtt/lib/mqtt_msg.c"
|
||||||
"esp-mqtt/lib/mqtt_outbox.c"
|
"esp-mqtt/lib/mqtt_outbox.c"
|
||||||
"esp-mqtt/lib/platform_esp32_idf.c"
|
"esp-mqtt/lib/platform_esp32_idf.c")
|
||||||
"esp-mqtt/lib/transport_ws.c")
|
|
||||||
|
|
||||||
set(COMPONENT_REQUIRES lwip nghttp mbedtls tcp_transport)
|
set(COMPONENT_REQUIRES lwip nghttp mbedtls tcp_transport)
|
||||||
|
|
||||||
register_component()
|
register_component()
|
||||||
|
|
||||||
if(GCC_NOT_5_2_0)
|
|
||||||
# Temporary suppress "format-overflow" warning until we are fixed in esp-mqtt repo
|
|
||||||
set_source_files_properties(
|
|
||||||
esp-mqtt/lib/transport_ws.c
|
|
||||||
PROPERTIES COMPILE_FLAGS
|
|
||||||
-Wno-format-overflow)
|
|
||||||
endif()
|
|
||||||
|
Submodule components/mqtt/esp-mqtt updated: bcb38e45f5...ca893e2c23
@@ -1,10 +1,19 @@
|
|||||||
set(COMPONENT_SRCS "transport.c"
|
set(COMPONENT_SRCS "transport.c"
|
||||||
"transport_ssl.c"
|
"transport_ssl.c"
|
||||||
"transport_tcp.c"
|
"transport_tcp.c"
|
||||||
|
"transport_ws.c"
|
||||||
"transport_utils.c")
|
"transport_utils.c")
|
||||||
|
|
||||||
set(COMPONENT_ADD_INCLUDEDIRS "include")
|
set(COMPONENT_ADD_INCLUDEDIRS "include")
|
||||||
|
|
||||||
set(COMPONENT_REQUIRES lwip esp-tls)
|
set(COMPONENT_REQUIRES lwip esp-tls)
|
||||||
|
|
||||||
register_component()
|
register_component()
|
||||||
|
|
||||||
|
if(GCC_NOT_5_2_0)
|
||||||
|
# Temporary suppress "format-overflow" warning until it is fixed
|
||||||
|
set_source_files_properties(
|
||||||
|
transport_ws.c
|
||||||
|
PROPERTIES COMPILE_FLAGS
|
||||||
|
-Wno-format-overflow)
|
||||||
|
endif()
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
#ifndef _TRANSPORT_SSL_H_
|
#ifndef _TRANSPORT_SSL_H_
|
||||||
#define _TRANSPORT_SSL_H_
|
#define _TRANSPORT_SSL_H_
|
||||||
|
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
@@ -15,7 +15,7 @@
|
|||||||
#ifndef _TRANSPORT_TCP_H_
|
#ifndef _TRANSPORT_TCP_H_
|
||||||
#define _TRANSPORT_TCP_H_
|
#define _TRANSPORT_TCP_H_
|
||||||
|
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
46
components/tcp_transport/include/esp_transport_ws.h
Normal file
46
components/tcp_transport/include/esp_transport_ws.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is subject to the terms and conditions defined in
|
||||||
|
* file 'LICENSE', which is part of this source code package.
|
||||||
|
* Tuan PM <tuanpm at live dot com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TRANSPORT_WS_H_
|
||||||
|
#define _TRANSPORT_WS_H_
|
||||||
|
|
||||||
|
#include "esp_transport.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define WS_FIN 0x80
|
||||||
|
#define WS_OPCODE_TEXT 0x01
|
||||||
|
#define WS_OPCODE_BINARY 0x02
|
||||||
|
#define WS_OPCODE_CLOSE 0x08
|
||||||
|
#define WS_OPCODE_PING 0x09
|
||||||
|
#define WS_OPCODE_PONG 0x0a
|
||||||
|
// Second byte
|
||||||
|
#define WS_MASK 0x80
|
||||||
|
#define WS_SIZE16 126
|
||||||
|
#define WS_SIZE64 127
|
||||||
|
#define MAX_WEBSOCKET_HEADER_SIZE 10
|
||||||
|
#define WS_RESPONSE_OK 101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create TCP transport
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - transport
|
||||||
|
* - NULL
|
||||||
|
*/
|
||||||
|
transport_handle_t transport_ws_init(transport_handle_t parent_handle);
|
||||||
|
|
||||||
|
void transport_ws_set_path(transport_handle_t t, const char *path);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@@ -19,8 +19,8 @@
|
|||||||
#include "rom/queue.h"
|
#include "rom/queue.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
#include "transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
|
|
||||||
static const char *TAG = "TRANSPORT";
|
static const char *TAG = "TRANSPORT";
|
||||||
|
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
|
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
#include "transport_ssl.h"
|
#include "esp_transport_ssl.h"
|
||||||
#include "transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
|
|
||||||
static const char *TAG = "TRANS_SSL";
|
static const char *TAG = "TRANS_SSL";
|
||||||
|
|
||||||
|
@@ -23,8 +23,8 @@
|
|||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
#include "transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
#include "transport.h"
|
#include "esp_transport.h"
|
||||||
|
|
||||||
static const char *TAG = "TRANS_TCP";
|
static const char *TAG = "TRANS_TCP";
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "transport_utils.h"
|
#include "esp_transport_utils.h"
|
||||||
|
|
||||||
void transport_utils_ms_to_timeval(int timeout_ms, struct timeval *tv)
|
void transport_utils_ms_to_timeval(int timeout_ms, struct timeval *tv)
|
||||||
{
|
{
|
||||||
|
264
components/tcp_transport/transport_ws.c
Normal file
264
components/tcp_transport/transport_ws.c
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_transport.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "esp_transport_ws.h"
|
||||||
|
#include "esp_transport_utils.h"
|
||||||
|
#include "mbedtls/base64.h"
|
||||||
|
#include "mbedtls/sha1.h"
|
||||||
|
|
||||||
|
static const char *TAG = "TRANSPORT_WS";
|
||||||
|
|
||||||
|
#define DEFAULT_WS_BUFFER (1024)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *path;
|
||||||
|
char *buffer;
|
||||||
|
transport_handle_t parent;
|
||||||
|
} transport_ws_t;
|
||||||
|
|
||||||
|
transport_handle_t ws_transport_get_payload_transport_handle(transport_handle_t t)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
return ws->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *trimwhitespace(const char *str)
|
||||||
|
{
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
// Trim leading space
|
||||||
|
while (isspace((unsigned char)*str)) str++;
|
||||||
|
|
||||||
|
if (*str == 0) {
|
||||||
|
return (char *)str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing space
|
||||||
|
end = (char *)(str + strlen(str) - 1);
|
||||||
|
while (end > str && isspace((unsigned char)*end)) end--;
|
||||||
|
|
||||||
|
// Write new null terminator
|
||||||
|
*(end + 1) = 0;
|
||||||
|
|
||||||
|
return (char *)str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *get_http_header(const char *buffer, const char *key)
|
||||||
|
{
|
||||||
|
char *found = strstr(buffer, key);
|
||||||
|
if (found) {
|
||||||
|
found += strlen(key);
|
||||||
|
char *found_end = strstr(found, "\r\n");
|
||||||
|
if (found_end) {
|
||||||
|
found_end[0] = 0;//terminal string
|
||||||
|
|
||||||
|
return trimwhitespace(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
if (transport_connect(ws->parent, host, port, timeout_ms) < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error connect to ther server");
|
||||||
|
}
|
||||||
|
unsigned char random_key[16] = { 0 }, client_key[32] = {0};
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < sizeof(random_key); i++) {
|
||||||
|
random_key[i] = rand() & 0xFF;
|
||||||
|
}
|
||||||
|
size_t outlen = 0;
|
||||||
|
mbedtls_base64_encode(client_key, 32, &outlen, random_key, 16);
|
||||||
|
int len = snprintf(ws->buffer, DEFAULT_WS_BUFFER,
|
||||||
|
"GET %s HTTP/1.1\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Host: %s:%d\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Sec-WebSocket-Version: 13\r\n"
|
||||||
|
"Sec-WebSocket-Protocol: mqtt\r\n"
|
||||||
|
"Sec-WebSocket-Key: %s\r\n"
|
||||||
|
"User-Agent: ESP32 MQTT Client\r\n\r\n",
|
||||||
|
ws->path,
|
||||||
|
host, port,
|
||||||
|
client_key);
|
||||||
|
ESP_LOGD(TAG, "Write upgrate request\r\n%s", ws->buffer);
|
||||||
|
if (transport_write(ws->parent, ws->buffer, len, timeout_ms) <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Error write Upgrade header %s", ws->buffer);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if ((len = transport_read(ws->parent, ws->buffer, DEFAULT_WS_BUFFER, timeout_ms)) <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Error read response for Upgrade header %s", ws->buffer);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
char *server_key = get_http_header(ws->buffer, "Sec-WebSocket-Accept:");
|
||||||
|
if (server_key == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Sec-WebSocket-Accept not found");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char client_key_b64[64], valid_client_key[20], accept_key[32] = {0};
|
||||||
|
int key_len = sprintf((char*)client_key_b64, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", (char*)client_key);
|
||||||
|
mbedtls_sha1_ret(client_key_b64, (size_t)key_len, valid_client_key);
|
||||||
|
mbedtls_base64_encode(accept_key, 32, &outlen, valid_client_key, 20);
|
||||||
|
accept_key[outlen] = 0;
|
||||||
|
ESP_LOGD(TAG, "server key=%s, send_key=%s, accept_key=%s", (char *)server_key, (char*)client_key, accept_key);
|
||||||
|
if (strcmp((char*)accept_key, (char*)server_key) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Invalid websocket key");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_write(transport_handle_t t, const char *buff, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
char ws_header[MAX_WEBSOCKET_HEADER_SIZE];
|
||||||
|
char *mask;
|
||||||
|
int header_len = 0, i;
|
||||||
|
char *buffer = (char *)buff;
|
||||||
|
int poll_write;
|
||||||
|
if ((poll_write = transport_poll_write(ws->parent, timeout_ms)) <= 0) {
|
||||||
|
return poll_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_header[header_len++] = WS_OPCODE_BINARY | WS_FIN;
|
||||||
|
|
||||||
|
// NOTE: no support for > 16-bit sized messages
|
||||||
|
if (len > 125) {
|
||||||
|
ws_header[header_len++] = WS_SIZE16 | WS_MASK;
|
||||||
|
ws_header[header_len++] = (uint8_t)(len >> 8);
|
||||||
|
ws_header[header_len++] = (uint8_t)(len & 0xFF);
|
||||||
|
} else {
|
||||||
|
ws_header[header_len++] = (uint8_t)(len | WS_MASK);
|
||||||
|
}
|
||||||
|
mask = &ws_header[header_len];
|
||||||
|
ws_header[header_len++] = rand() & 0xFF;
|
||||||
|
ws_header[header_len++] = rand() & 0xFF;
|
||||||
|
ws_header[header_len++] = rand() & 0xFF;
|
||||||
|
ws_header[header_len++] = rand() & 0xFF;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
buffer[i] = (buffer[i] ^ mask[i % 4]);
|
||||||
|
}
|
||||||
|
if (transport_write(ws->parent, ws_header, header_len, timeout_ms) != header_len) {
|
||||||
|
ESP_LOGE(TAG, "Error write header");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return transport_write(ws->parent, buffer, len, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
int payload_len;
|
||||||
|
int payload_len_buff = len;
|
||||||
|
char *data_ptr = buffer, opcode, mask, *mask_key = NULL;
|
||||||
|
int rlen;
|
||||||
|
int poll_read;
|
||||||
|
if ((poll_read = transport_poll_read(ws->parent, timeout_ms)) <= 0) {
|
||||||
|
return poll_read;
|
||||||
|
}
|
||||||
|
if ((rlen = transport_read(ws->parent, buffer, len, timeout_ms)) <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Error read data");
|
||||||
|
return rlen;
|
||||||
|
}
|
||||||
|
opcode = (*data_ptr & 0x0F);
|
||||||
|
data_ptr ++;
|
||||||
|
mask = ((*data_ptr >> 7) & 0x01);
|
||||||
|
payload_len = (*data_ptr & 0x7F);
|
||||||
|
data_ptr++;
|
||||||
|
ESP_LOGD(TAG, "Opcode: %d, mask: %d, len: %d\r\n", opcode, mask, payload_len);
|
||||||
|
if (payload_len == 126) {
|
||||||
|
// headerLen += 2;
|
||||||
|
payload_len = data_ptr[0] << 8 | data_ptr[1];
|
||||||
|
payload_len_buff = len - 4;
|
||||||
|
data_ptr += 2;
|
||||||
|
} else if (payload_len == 127) {
|
||||||
|
// headerLen += 8;
|
||||||
|
|
||||||
|
if (data_ptr[0] != 0 || data_ptr[1] != 0 || data_ptr[2] != 0 || data_ptr[3] != 0) {
|
||||||
|
// really too big!
|
||||||
|
payload_len = 0xFFFFFFFF;
|
||||||
|
} else {
|
||||||
|
payload_len = data_ptr[4] << 24 | data_ptr[5] << 16 | data_ptr[6] << 8 | data_ptr[7];
|
||||||
|
}
|
||||||
|
data_ptr += 8;
|
||||||
|
payload_len_buff = len - 10;
|
||||||
|
}
|
||||||
|
if (payload_len > payload_len_buff) {
|
||||||
|
ESP_LOGD(TAG, "Actual data received (%d) are longer than mqtt buffer (%d)", payload_len, payload_len_buff);
|
||||||
|
payload_len = payload_len_buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask) {
|
||||||
|
mask_key = data_ptr;
|
||||||
|
data_ptr += 4;
|
||||||
|
for (int i = 0; i < payload_len; i++) {
|
||||||
|
buffer[i] = (data_ptr[i] ^ mask_key[i % 4]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memmove(buffer, data_ptr, payload_len);
|
||||||
|
}
|
||||||
|
return payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_poll_read(transport_handle_t t, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
return transport_poll_read(ws->parent, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_poll_write(transport_handle_t t, int timeout_ms)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
return transport_poll_write(ws->parent, timeout_ms);;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_close(transport_handle_t t)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
return transport_close(ws->parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t ws_destroy(transport_handle_t t)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
free(ws->buffer);
|
||||||
|
free(ws->path);
|
||||||
|
free(ws);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void transport_ws_set_path(transport_handle_t t, const char *path)
|
||||||
|
{
|
||||||
|
transport_ws_t *ws = transport_get_context_data(t);
|
||||||
|
ws->path = realloc(ws->path, strlen(path) + 1);
|
||||||
|
strcpy(ws->path, path);
|
||||||
|
}
|
||||||
|
transport_handle_t transport_ws_init(transport_handle_t parent_handle)
|
||||||
|
{
|
||||||
|
transport_handle_t t = transport_init();
|
||||||
|
transport_ws_t *ws = calloc(1, sizeof(transport_ws_t));
|
||||||
|
TRANSPORT_MEM_CHECK(TAG, ws, return NULL);
|
||||||
|
ws->parent = parent_handle;
|
||||||
|
|
||||||
|
ws->path = strdup("/");
|
||||||
|
TRANSPORT_MEM_CHECK(TAG, ws->path, return NULL);
|
||||||
|
ws->buffer = malloc(DEFAULT_WS_BUFFER);
|
||||||
|
TRANSPORT_MEM_CHECK(TAG, ws->buffer, {
|
||||||
|
free(ws->path);
|
||||||
|
free(ws);
|
||||||
|
return NULL;
|
||||||
|
});
|
||||||
|
|
||||||
|
transport_set_func(t, ws_connect, ws_read, ws_write, ws_close, ws_poll_read, ws_poll_write, ws_destroy, ws_transport_get_payload_transport_handle);
|
||||||
|
transport_set_context_data(t, ws);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user