/*
* 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 .
*
*/
#include "platform.h"
#ifdef WIN32
#include
#else
/*
#include
#include
#include
#include
*/
#include
#endif
#include
#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;
}