190 lines
4.7 KiB
C
190 lines
4.7 KiB
C
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "ota_url_policy.h"
|
|
|
|
#define BANBAN_OTA_LEGACY_URL_PREFIX \
|
|
"http://101.35.224.118:8080/assets/firmware/"
|
|
#define BANBAN_OTA_BASE_DOMAIN "talkingq.com"
|
|
|
|
static int banban_ascii_equal_nocase(const char *left,
|
|
const char *right,
|
|
size_t length)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
unsigned char a = (unsigned char)left[i];
|
|
unsigned char b = (unsigned char)right[i];
|
|
|
|
if (a >= 'A' && a <= 'Z') {
|
|
a = (unsigned char)(a + ('a' - 'A'));
|
|
}
|
|
if (b >= 'A' && b <= 'Z') {
|
|
b = (unsigned char)(b + ('a' - 'A'));
|
|
}
|
|
if (a != b) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int banban_hostname_is_valid(const char *host, size_t host_length)
|
|
{
|
|
size_t i;
|
|
int label_has_character = 0;
|
|
int previous_was_hyphen = 0;
|
|
|
|
if (host_length == 0 || host_length > 253) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < host_length; ++i) {
|
|
unsigned char character = (unsigned char)host[i];
|
|
|
|
if ((character >= 'A' && character <= 'Z') ||
|
|
(character >= 'a' && character <= 'z') ||
|
|
(character >= '0' && character <= '9')) {
|
|
label_has_character = 1;
|
|
previous_was_hyphen = 0;
|
|
continue;
|
|
}
|
|
|
|
if (character == '-') {
|
|
if (!label_has_character) {
|
|
return 0;
|
|
}
|
|
previous_was_hyphen = 1;
|
|
continue;
|
|
}
|
|
|
|
if (character == '.') {
|
|
if (!label_has_character || previous_was_hyphen) {
|
|
return 0;
|
|
}
|
|
label_has_character = 0;
|
|
previous_was_hyphen = 0;
|
|
continue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return label_has_character && !previous_was_hyphen;
|
|
}
|
|
|
|
static int banban_host_is_talkingq(const char *host, size_t host_length)
|
|
{
|
|
const size_t base_length = sizeof(BANBAN_OTA_BASE_DOMAIN) - 1;
|
|
|
|
if (!banban_hostname_is_valid(host, host_length)) {
|
|
return 0;
|
|
}
|
|
|
|
if (host_length == base_length) {
|
|
return banban_ascii_equal_nocase(host,
|
|
BANBAN_OTA_BASE_DOMAIN,
|
|
base_length);
|
|
}
|
|
|
|
if (host_length <= base_length + 1 ||
|
|
host[host_length - base_length - 1] != '.') {
|
|
return 0;
|
|
}
|
|
|
|
return banban_ascii_equal_nocase(host + host_length - base_length,
|
|
BANBAN_OTA_BASE_DOMAIN,
|
|
base_length);
|
|
}
|
|
|
|
static int banban_port_is_valid(const char *port, size_t port_length)
|
|
{
|
|
size_t i;
|
|
unsigned long value = 0;
|
|
|
|
if (port_length == 0 || port_length > 5) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < port_length; ++i) {
|
|
if (port[i] < '0' || port[i] > '9') {
|
|
return 0;
|
|
}
|
|
value = value * 10 + (unsigned long)(port[i] - '0');
|
|
}
|
|
|
|
return value > 0 && value <= 65535;
|
|
}
|
|
|
|
static int banban_path_is_fbf(const char *path)
|
|
{
|
|
const char *path_end;
|
|
const char *query;
|
|
|
|
if (path == NULL || path[0] != '/' || path[1] == '\0' ||
|
|
strstr(path, "..") != NULL || strchr(path, '\\') != NULL ||
|
|
strchr(path, '#') != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
query = strchr(path, '?');
|
|
path_end = query != NULL ? query : path + strlen(path);
|
|
if ((size_t)(path_end - path) < 4) {
|
|
return 0;
|
|
}
|
|
|
|
return banban_ascii_equal_nocase(path_end - 4, ".bin", 4);
|
|
}
|
|
|
|
int banban_ota_url_is_allowed(const char *url)
|
|
{
|
|
const char *authority;
|
|
const char *path;
|
|
const char *host_end;
|
|
const char *colon;
|
|
const size_t legacy_prefix_length =
|
|
sizeof(BANBAN_OTA_LEGACY_URL_PREFIX) - 1;
|
|
|
|
if (url == NULL || url[0] == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
if (strncmp(url, BANBAN_OTA_LEGACY_URL_PREFIX,
|
|
legacy_prefix_length) == 0) {
|
|
return banban_path_is_fbf(url + legacy_prefix_length - 1);
|
|
}
|
|
|
|
if (strncmp(url, "http://", 7) == 0) {
|
|
authority = url + 7;
|
|
} else if (strncmp(url, "https://", 8) == 0) {
|
|
authority = url + 8;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
path = strchr(authority, '/');
|
|
if (path == NULL || path == authority) {
|
|
return 0;
|
|
}
|
|
|
|
if (memchr(authority, '@', (size_t)(path - authority)) != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
colon = memchr(authority, ':', (size_t)(path - authority));
|
|
host_end = colon != NULL ? colon : path;
|
|
if (!banban_host_is_talkingq(authority,
|
|
(size_t)(host_end - authority))) {
|
|
return 0;
|
|
}
|
|
|
|
if (colon != NULL &&
|
|
!banban_port_is_valid(colon + 1, (size_t)(path - colon - 1))) {
|
|
return 0;
|
|
}
|
|
|
|
return banban_path_is_fbf(path);
|
|
}
|