mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 11:36:59 +03:00
602 lines
13 KiB
C
602 lines
13 KiB
C
/*
|
|
* AirConnect: Chromecast & UPnP to AirPlay
|
|
*
|
|
* (c) Philippe 2016-2017, philippe_44@outlook.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "platform.h"
|
|
|
|
#ifdef WIN32
|
|
#include <iphlpapi.h>
|
|
#else
|
|
/*
|
|
#include <sys/ioctl.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <netdb.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
|
|
// missing platform here ...
|
|
return INADDR_ANY;
|
|
#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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|