mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-03-21 05:49:26 +00:00
558 lines
14 KiB
C
558 lines
14 KiB
C
/*
|
|
* AirConnect: Chromecast & UPnP to AirPlay
|
|
*
|
|
* (c) Philippe 2016-2017, philippe_44@outlook.com
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*
|
|
*/
|
|
|
|
#include "platform.h"
|
|
|
|
#ifdef WIN32
|
|
#include <iphlpapi.h>
|
|
#else
|
|
#include "esp_netif.h"
|
|
#include "tcpip_adapter_compat.h"
|
|
// IDF-V4++ #include "esp_netif.h"
|
|
#include <ctype.h>
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include "pthread.h"
|
|
#include "util.h"
|
|
#include "log_util.h"
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* globals */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
extern log_level util_loglevel;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* locals */
|
|
/*----------------------------------------------------------------------------*/
|
|
static log_level* loglevel = &util_loglevel;
|
|
|
|
static char* ltrim(char* s);
|
|
static int read_line(int fd, char* line, int maxlen, int timeout);
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* */
|
|
/* NETWORKING utils */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
#define MAX_INTERFACES 256
|
|
#define DEFAULT_INTERFACE 1
|
|
#if !defined(WIN32)
|
|
#define INVALID_SOCKET (-1)
|
|
#endif
|
|
in_addr_t get_localhost(char** name) {
|
|
#ifdef WIN32
|
|
char buf[256];
|
|
struct hostent* h = NULL;
|
|
struct sockaddr_in LocalAddr;
|
|
|
|
memset(&LocalAddr, 0, sizeof(LocalAddr));
|
|
|
|
gethostname(buf, 256);
|
|
h = gethostbyname(buf);
|
|
|
|
if(name) *name = strdup(buf);
|
|
|
|
if(h != NULL) {
|
|
memcpy(&LocalAddr.sin_addr, h->h_addr_list[0], 4);
|
|
return LocalAddr.sin_addr.s_addr;
|
|
} else
|
|
return INADDR_ANY;
|
|
#else
|
|
tcpip_adapter_ip_info_t ipInfo;
|
|
tcpip_adapter_if_t if_type = TCPIP_ADAPTER_IF_STA;
|
|
|
|
// then get IP address
|
|
tcpip_adapter_get_ip_info(if_type, &ipInfo);
|
|
|
|
// we might be in AP mode
|
|
if(ipInfo.ip.addr == INADDR_ANY) {
|
|
if_type = TCPIP_ADAPTER_IF_AP;
|
|
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo);
|
|
}
|
|
|
|
// get hostname if required
|
|
if(name) {
|
|
const char* hostname;
|
|
tcpip_adapter_get_hostname(if_type, &hostname);
|
|
*name = strdup(hostname);
|
|
}
|
|
|
|
return ipInfo.ip.addr;
|
|
#endif
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
#ifdef WIN32
|
|
void winsock_init(void) {
|
|
WSADATA wsaData;
|
|
WORD wVersionRequested = MAKEWORD(2, 2);
|
|
int WSerr = WSAStartup(wVersionRequested, &wsaData);
|
|
if(WSerr != 0) {
|
|
LOG_ERROR("Bad winsock version", NULL);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
void winsock_close(void) { WSACleanup(); }
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
int shutdown_socket(int sd) {
|
|
if(sd <= 0) return -1;
|
|
|
|
#ifdef WIN32
|
|
shutdown(sd, SD_BOTH);
|
|
#else
|
|
shutdown(sd, SHUT_RDWR);
|
|
#endif
|
|
|
|
LOG_DEBUG("closed socket %d", sd);
|
|
|
|
return closesocket(sd);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
int bind_socket(unsigned short* port, int mode) {
|
|
int sock;
|
|
socklen_t len = sizeof(struct sockaddr);
|
|
struct sockaddr_in addr;
|
|
|
|
if((sock = socket(AF_INET, mode, 0)) < 0) {
|
|
LOG_ERROR("cannot create socket %d", sock);
|
|
return sock;
|
|
}
|
|
|
|
/* Populate socket address structure */
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
addr.sin_port = htons(*port);
|
|
#ifdef SIN_LEN
|
|
si.sin_len = sizeof(si);
|
|
#endif
|
|
|
|
if(bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
closesocket(sock);
|
|
LOG_ERROR("cannot bind socket %d", sock);
|
|
return -1;
|
|
}
|
|
|
|
if(!*port) {
|
|
getsockname(sock, (struct sockaddr*)&addr, &len);
|
|
*port = ntohs(addr.sin_port);
|
|
}
|
|
|
|
LOG_DEBUG("socket binding %d on port %d", sock, *port);
|
|
|
|
return sock;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
int conn_socket(unsigned short port) {
|
|
struct sockaddr_in addr;
|
|
int sd;
|
|
|
|
sd = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
addr.sin_port = htons(port);
|
|
|
|
if(sd < 0 || connect(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
close(sd);
|
|
return -1;
|
|
}
|
|
|
|
LOG_DEBUG("created socket %d", sd);
|
|
|
|
return sd;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* */
|
|
/* SYSTEM utils */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#ifdef WIN32
|
|
/*----------------------------------------------------------------------------*/
|
|
void* dlopen(const char* filename, int flag) {
|
|
SetLastError(0);
|
|
return LoadLibrary((LPCTSTR)filename);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
void* dlsym(void* handle, const char* symbol) {
|
|
SetLastError(0);
|
|
return (void*)GetProcAddress(handle, symbol);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
char* dlerror(void) {
|
|
static char ret[32];
|
|
int last = GetLastError();
|
|
if(last) {
|
|
sprintf(ret, "code: %i", last);
|
|
SetLastError(0);
|
|
return ret;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* */
|
|
/* STDLIB extensions */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#ifdef WIN32
|
|
/*---------------------------------------------------------------------------*/
|
|
char* strcasestr(const char* haystack, const char* needle) {
|
|
size_t length_needle;
|
|
size_t length_haystack;
|
|
size_t i;
|
|
|
|
if(!haystack || !needle) return NULL;
|
|
|
|
length_needle = strlen(needle);
|
|
length_haystack = strlen(haystack);
|
|
|
|
if(length_haystack < length_needle) return NULL;
|
|
|
|
length_haystack -= length_needle - 1;
|
|
|
|
for(i = 0; i < length_haystack; i++) {
|
|
size_t j;
|
|
|
|
for(j = 0; j < length_needle; j++) {
|
|
unsigned char c1;
|
|
unsigned char c2;
|
|
|
|
c1 = haystack[i + j];
|
|
c2 = needle[j];
|
|
if(toupper(c1) != toupper(c2)) goto next;
|
|
}
|
|
return (char*)haystack + i;
|
|
next:;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
char* strsep(char** stringp, const char* delim) {
|
|
char* start = *stringp;
|
|
char* p;
|
|
|
|
p = (start != NULL) ? strpbrk(start, delim) : NULL;
|
|
|
|
if(p == NULL) {
|
|
*stringp = NULL;
|
|
} else {
|
|
*p = '\0';
|
|
*stringp = p + 1;
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
char* strndup(const char* s, size_t n) {
|
|
char* p = malloc(n + 1);
|
|
strncpy(p, s, n);
|
|
p[n] = '\0';
|
|
|
|
return p;
|
|
}
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
char* strextract(char* s1, char* beg, char* end) {
|
|
char *p1, *p2, *res;
|
|
|
|
p1 = strcasestr(s1, beg);
|
|
if(!p1) return NULL;
|
|
|
|
p1 += strlen(beg);
|
|
p2 = strcasestr(p1, end);
|
|
if(!p2) return strdup(p1);
|
|
|
|
res = malloc(p2 - p1 + 1);
|
|
memcpy(res, p1, p2 - p1);
|
|
res[p2 - p1] = '\0';
|
|
|
|
return res;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/*----------------------------------------------------------------------------*/
|
|
int asprintf(char** strp, const char* fmt, ...) {
|
|
va_list args, cp;
|
|
int len, ret = 0;
|
|
|
|
va_start(args, fmt);
|
|
len = vsnprintf(NULL, 0, fmt, args);
|
|
*strp = malloc(len + 1);
|
|
|
|
if(*strp) ret = vsprintf(*strp, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static char* ltrim(char* s) {
|
|
while(isspace((int)*s)) s++;
|
|
return s;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* */
|
|
/* HTTP management */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
bool http_parse(int sock, char* method, key_data_t* rkd, char** body, int* len) {
|
|
char line[256], *dp;
|
|
unsigned j;
|
|
int i, timeout = 100;
|
|
|
|
rkd[0].key = NULL;
|
|
|
|
if((i = read_line(sock, line, sizeof(line), timeout)) <= 0) {
|
|
if(i < 0) { LOG_ERROR("cannot read method", NULL); }
|
|
return false;
|
|
}
|
|
|
|
if(!sscanf(line, "%s", method)) {
|
|
LOG_ERROR("missing method", NULL);
|
|
return false;
|
|
}
|
|
|
|
i = *len = 0;
|
|
|
|
while(read_line(sock, line, sizeof(line), timeout) > 0) {
|
|
|
|
LOG_SDEBUG("sock: %u, received %s", line);
|
|
|
|
// line folding should be deprecated
|
|
if(i && rkd[i].key && (line[0] == ' ' || line[0] == '\t')) {
|
|
for(j = 0; j < strlen(line); j++)
|
|
if(line[j] != ' ' && line[j] != '\t') break;
|
|
rkd[i].data = realloc(rkd[i].data, strlen(rkd[i].data) + strlen(line + j) + 1);
|
|
strcat(rkd[i].data, line + j);
|
|
continue;
|
|
}
|
|
|
|
dp = strstr(line, ":");
|
|
|
|
if(!dp) {
|
|
LOG_ERROR("Request failed, bad header", NULL);
|
|
kd_free(rkd);
|
|
return false;
|
|
}
|
|
|
|
*dp = 0;
|
|
rkd[i].key = strdup(line);
|
|
rkd[i].data = strdup(ltrim(dp + 1));
|
|
|
|
if(!strcasecmp(rkd[i].key, "Content-Length")) *len = atol(rkd[i].data);
|
|
|
|
i++;
|
|
rkd[i].key = NULL;
|
|
}
|
|
|
|
if(*len) {
|
|
int size = 0;
|
|
|
|
*body = malloc(*len + 1);
|
|
while(*body && size < *len) {
|
|
int bytes = recv(sock, *body + size, *len - size, 0);
|
|
if(bytes <= 0) break;
|
|
size += bytes;
|
|
}
|
|
|
|
(*body)[*len] = '\0';
|
|
|
|
if(!*body || size != *len) { LOG_ERROR("content length receive error %d %d", *len, size); }
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
static int read_line(int fd, char* line, int maxlen, int timeout) {
|
|
int i, rval;
|
|
int count = 0;
|
|
struct pollfd pfds;
|
|
char ch;
|
|
|
|
*line = 0;
|
|
pfds.fd = fd;
|
|
pfds.events = POLLIN;
|
|
|
|
for(i = 0; i < maxlen; i++) {
|
|
if(poll(&pfds, 1, timeout))
|
|
rval = recv(fd, &ch, 1, 0);
|
|
else
|
|
return 0;
|
|
|
|
if(rval == -1) {
|
|
if(errno == EAGAIN) return 0;
|
|
LOG_ERROR("fd: %d read error: %s", fd, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(rval == 0) {
|
|
LOG_INFO("disconnected on the other end %u", fd);
|
|
return 0;
|
|
}
|
|
|
|
if(ch == '\n') {
|
|
*line = 0;
|
|
return count;
|
|
}
|
|
|
|
if(ch == '\r') continue;
|
|
|
|
*line++ = ch;
|
|
count++;
|
|
if(count >= maxlen - 1) break;
|
|
}
|
|
|
|
*line = 0;
|
|
return count;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
char* http_send(int sock, char* method, key_data_t* rkd) {
|
|
unsigned sent, len;
|
|
char* resp = kd_dump(rkd);
|
|
char* data = malloc(strlen(method) + 2 + strlen(resp) + 2 + 1);
|
|
|
|
len = sprintf(data, "%s\r\n%s\r\n", method, resp);
|
|
NFREE(resp);
|
|
|
|
sent = send(sock, data, len, 0);
|
|
|
|
if(sent != len) {
|
|
LOG_ERROR("HTTP send() error:%s %u (strlen=%u)", data, sent, len);
|
|
NFREE(data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
char* kd_lookup(key_data_t* kd, char* key) {
|
|
int i = 0;
|
|
while(kd && kd[i].key) {
|
|
if(!strcasecmp(kd[i].key, key)) return kd[i].data;
|
|
i++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
bool kd_add(key_data_t* kd, char* key, char* data) {
|
|
int i = 0;
|
|
while(kd && kd[i].key) i++;
|
|
|
|
kd[i].key = strdup(key);
|
|
kd[i].data = strdup(data);
|
|
kd[i + 1].key = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
void kd_free(key_data_t* kd) {
|
|
int i = 0;
|
|
while(kd && kd[i].key) {
|
|
free(kd[i].key);
|
|
if(kd[i].data) free(kd[i].data);
|
|
i++;
|
|
}
|
|
|
|
kd[0].key = NULL;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
char* kd_dump(key_data_t* kd) {
|
|
int i = 0;
|
|
int pos = 0, size = 0;
|
|
char* str = NULL;
|
|
|
|
if(!kd || !kd[0].key) return strdup("\r\n");
|
|
|
|
while(kd && kd[i].key) {
|
|
char* buf;
|
|
int len;
|
|
|
|
len = asprintf(&buf, "%s: %s\r\n", kd[i].key, kd[i].data);
|
|
|
|
while(pos + len >= size) {
|
|
void* p = realloc(str, size + 1024);
|
|
size += 1024;
|
|
if(!p) {
|
|
free(str);
|
|
return NULL;
|
|
}
|
|
str = p;
|
|
}
|
|
|
|
memcpy(str + pos, buf, len);
|
|
|
|
pos += len;
|
|
free(buf);
|
|
i++;
|
|
}
|
|
|
|
str[pos] = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
void free_metadata(struct metadata_s* metadata) {
|
|
NFREE(metadata->artist);
|
|
NFREE(metadata->album);
|
|
NFREE(metadata->title);
|
|
NFREE(metadata->genre);
|
|
NFREE(metadata->path);
|
|
NFREE(metadata->artwork);
|
|
NFREE(metadata->remote_title);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
int _fprintf(FILE* file, ...) {
|
|
va_list args;
|
|
char* fmt;
|
|
int n;
|
|
|
|
va_start(args, file);
|
|
fmt = va_arg(args, char*);
|
|
|
|
n = vfprintf(file, fmt, args);
|
|
va_end(args);
|
|
return n;
|
|
}
|