417 lines
10 KiB
C++
417 lines
10 KiB
C++
/*
|
|
* http.c : GeeXboX uShare Web Server handler.
|
|
* Originally developped for the GeeXboX project.
|
|
* Parts of the code are originated from GMediaServer from Oskar Liljeblad.
|
|
* Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org>
|
|
*
|
|
* 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 2 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 Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include <upnp/upnp.h>
|
|
#include <upnp/upnptools.h>
|
|
|
|
#include "services.h"
|
|
#include "cds.h"
|
|
#include "cms.h"
|
|
#include "msr.h"
|
|
#include "metadata.h"
|
|
#include "http.h"
|
|
#include "minmax.h"
|
|
#include "trace.h"
|
|
#include "presentation.h"
|
|
#include "osdep.h"
|
|
#include "mime.h"
|
|
|
|
#define PROTOCOL_TYPE_PRE_SZ 11 /* for the str length of "http-get:*:" */
|
|
#define PROTOCOL_TYPE_SUFF_SZ 2 /* for the str length of ":*" */
|
|
|
|
struct web_file_t {
|
|
char *fullpath;
|
|
off_t pos;
|
|
enum {
|
|
FILE_LOCAL,
|
|
FILE_MEMORY
|
|
} type;
|
|
union {
|
|
struct {
|
|
int fd;
|
|
struct upnp_entry_t *entry;
|
|
} local;
|
|
struct {
|
|
char *contents;
|
|
off_t len;
|
|
} memory;
|
|
} detail;
|
|
};
|
|
|
|
|
|
static void
|
|
set_info_file (struct File_Info *info, const size_t length,
|
|
const char *content_type)
|
|
{
|
|
info->file_length = length;
|
|
info->last_modified = 0;
|
|
info->is_directory = 0;
|
|
info->is_readable = 1;
|
|
info->content_type = ixmlCloneDOMString (content_type);
|
|
}
|
|
|
|
static int
|
|
http_get_info (const char *filename, struct File_Info *info)
|
|
{
|
|
extern struct ushare_t *ut;
|
|
struct upnp_entry_t *entry = NULL;
|
|
struct stat st;
|
|
int upnp_id = 0;
|
|
char *content_type = NULL;
|
|
char *protocol = NULL;
|
|
|
|
if (!filename || !info)
|
|
return -1;
|
|
|
|
log_verbose ("http_get_info, filename : %s\n", filename);
|
|
|
|
if (!strcmp (filename, CDS_LOCATION))
|
|
{
|
|
set_info_file (info, CDS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE);
|
|
return 0;
|
|
}
|
|
|
|
if (!strcmp (filename, CMS_LOCATION))
|
|
{
|
|
set_info_file (info, CMS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE);
|
|
return 0;
|
|
}
|
|
|
|
if (!strcmp (filename, MSR_LOCATION))
|
|
{
|
|
set_info_file (info, MSR_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE);
|
|
return 0;
|
|
}
|
|
|
|
if (ut->use_presentation && !strcmp (filename, USHARE_PRESENTATION_PAGE))
|
|
{
|
|
if (build_presentation_page (ut) < 0)
|
|
return -1;
|
|
|
|
set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE);
|
|
return 0;
|
|
}
|
|
|
|
if (ut->use_presentation && !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI)))
|
|
{
|
|
if (process_cgi (ut, (char *) (filename + strlen (USHARE_CGI) + 1)) < 0)
|
|
return -1;
|
|
|
|
set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE);
|
|
return 0;
|
|
}
|
|
|
|
upnp_id = atoi (strrchr (filename, '/') + 1);
|
|
entry = upnp_get_entry (ut, upnp_id);
|
|
if (!entry)
|
|
return -1;
|
|
|
|
if (!entry->fullpath)
|
|
return -1;
|
|
|
|
if (stat (entry->fullpath, &st) < 0)
|
|
return -1;
|
|
|
|
if (access (entry->fullpath, R_OK) < 0)
|
|
{
|
|
if (errno != EACCES)
|
|
return -1;
|
|
info->is_readable = 0;
|
|
}
|
|
else
|
|
info->is_readable = 1;
|
|
|
|
/* file exist and can be read */
|
|
info->file_length = st.st_size;
|
|
info->last_modified = st.st_mtime;
|
|
info->is_directory = S_ISDIR (st.st_mode);
|
|
|
|
protocol =
|
|
#ifdef HAVE_DLNA
|
|
entry->dlna_profile ?
|
|
dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, entry->dlna_profile) :
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol (entry->mime_type);
|
|
|
|
content_type =
|
|
strndup ((protocol + PROTOCOL_TYPE_PRE_SZ),
|
|
strlen (protocol + PROTOCOL_TYPE_PRE_SZ)
|
|
- PROTOCOL_TYPE_SUFF_SZ);
|
|
free (protocol);
|
|
|
|
if (content_type)
|
|
{
|
|
info->content_type = ixmlCloneDOMString (content_type);
|
|
free (content_type);
|
|
}
|
|
else
|
|
info->content_type = ixmlCloneDOMString ("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static UpnpWebFileHandle
|
|
get_file_memory (const char *fullpath, const char *description,
|
|
const size_t length)
|
|
{
|
|
struct web_file_t *file;
|
|
|
|
file = (struct web_file_t *)malloc (sizeof (struct web_file_t));
|
|
file->fullpath = strdup (fullpath);
|
|
file->pos = 0;
|
|
file->type = web_file_t::FILE_MEMORY;
|
|
file->detail.memory.contents = strdup (description);
|
|
file->detail.memory.len = length;
|
|
|
|
return ((UpnpWebFileHandle) file);
|
|
}
|
|
|
|
static UpnpWebFileHandle
|
|
http_open (const char *filename, enum UpnpOpenFileMode mode)
|
|
{
|
|
extern struct ushare_t *ut;
|
|
struct upnp_entry_t *entry = NULL;
|
|
struct web_file_t *file;
|
|
int fd, upnp_id = 0;
|
|
|
|
if (!filename)
|
|
return NULL;
|
|
|
|
log_verbose ("http_open, filename : %s\n", filename);
|
|
|
|
if (mode != UPNP_READ)
|
|
return NULL;
|
|
|
|
if (!strcmp (filename, CDS_LOCATION))
|
|
return get_file_memory (CDS_LOCATION, CDS_DESCRIPTION, CDS_DESCRIPTION_LEN);
|
|
|
|
if (!strcmp (filename, CMS_LOCATION))
|
|
return get_file_memory (CMS_LOCATION, CMS_DESCRIPTION, CMS_DESCRIPTION_LEN);
|
|
|
|
if (!strcmp (filename, MSR_LOCATION))
|
|
return get_file_memory (MSR_LOCATION, MSR_DESCRIPTION, MSR_DESCRIPTION_LEN);
|
|
|
|
if (ut->use_presentation && ( !strcmp (filename, USHARE_PRESENTATION_PAGE)
|
|
|| !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI))))
|
|
return get_file_memory (USHARE_PRESENTATION_PAGE, ut->presentation->buf,
|
|
ut->presentation->len);
|
|
|
|
upnp_id = atoi (strrchr (filename, '/') + 1);
|
|
entry = upnp_get_entry (ut, upnp_id);
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
if (!entry->fullpath)
|
|
return NULL;
|
|
|
|
log_verbose ("Fullpath : %s\n", entry->fullpath);
|
|
|
|
fd = open (entry->fullpath, O_RDONLY | O_NONBLOCK | O_SYNC | O_NDELAY);
|
|
if (fd < 0)
|
|
return NULL;
|
|
|
|
file = (struct web_file_t *)malloc (sizeof (struct web_file_t));
|
|
file->fullpath = strdup (entry->fullpath);
|
|
file->pos = 0;
|
|
file->type = web_file_t::FILE_LOCAL;
|
|
file->detail.local.entry = entry;
|
|
file->detail.local.fd = fd;
|
|
|
|
return ((UpnpWebFileHandle) file);
|
|
}
|
|
|
|
static int
|
|
http_read (UpnpWebFileHandle fh, char *buf, size_t buflen)
|
|
{
|
|
struct web_file_t *file = (struct web_file_t *) fh;
|
|
ssize_t len = -1;
|
|
|
|
log_verbose ("http_read\n");
|
|
|
|
if (!file)
|
|
return -1;
|
|
|
|
switch (file->type)
|
|
{
|
|
case web_file_t::FILE_LOCAL:
|
|
log_verbose ("Read local file.\n");
|
|
len = read (file->detail.local.fd, buf, buflen);
|
|
break;
|
|
case web_file_t::FILE_MEMORY:
|
|
log_verbose ("Read file from memory.\n");
|
|
len = (size_t) MIN (buflen, file->detail.memory.len - file->pos);
|
|
memcpy (buf, file->detail.memory.contents + file->pos, (size_t) len);
|
|
break;
|
|
default:
|
|
log_verbose ("Unknown file type.\n");
|
|
break;
|
|
}
|
|
|
|
if (len >= 0)
|
|
file->pos += len;
|
|
|
|
log_verbose ("Read %zd bytes.\n", len);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
http_write (UpnpWebFileHandle fh __attribute__((unused)),
|
|
char *buf __attribute__((unused)),
|
|
size_t buflen __attribute__((unused)))
|
|
{
|
|
log_verbose ("http write\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
http_seek (UpnpWebFileHandle fh, off_t offset, int origin)
|
|
{
|
|
struct web_file_t *file = (struct web_file_t *) fh;
|
|
off_t newpos = -1;
|
|
|
|
log_verbose ("http_seek\n");
|
|
|
|
if (!file)
|
|
return -1;
|
|
|
|
switch (origin)
|
|
{
|
|
case SEEK_SET:
|
|
log_verbose ("Attempting to seek to %lld (was at %lld) in %s\n",
|
|
offset, file->pos, file->fullpath);
|
|
newpos = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
log_verbose ("Attempting to seek by %lld from %lld in %s\n",
|
|
offset, file->pos, file->fullpath);
|
|
newpos = file->pos + offset;
|
|
break;
|
|
case SEEK_END:
|
|
log_verbose ("Attempting to seek by %lld from end (was at %lld) in %s\n",
|
|
offset, file->pos, file->fullpath);
|
|
|
|
if (file->type == web_file_t::FILE_LOCAL)
|
|
{
|
|
struct stat sb;
|
|
if (stat (file->fullpath, &sb) < 0)
|
|
{
|
|
log_verbose ("%s: cannot stat: %s\n",
|
|
file->fullpath, strerror (errno));
|
|
return -1;
|
|
}
|
|
newpos = sb.st_size + offset;
|
|
} else if (file->type == web_file_t::FILE_MEMORY) {
|
|
newpos = file->detail.memory.len + offset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (file->type)
|
|
{
|
|
case web_file_t::FILE_LOCAL:
|
|
/* Just make sure we cannot seek before start of file. */
|
|
if (newpos < 0)
|
|
{
|
|
log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL));
|
|
return -1;
|
|
}
|
|
|
|
/* Don't seek with origin as specified above, as file may have
|
|
changed in size since our last stat. */
|
|
if (lseek (file->detail.local.fd, newpos, SEEK_SET) == -1)
|
|
{
|
|
log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (errno));
|
|
return -1;
|
|
}
|
|
break;
|
|
case web_file_t::FILE_MEMORY:
|
|
if (newpos < 0 || newpos > file->detail.memory.len)
|
|
{
|
|
log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL));
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
file->pos = newpos;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
http_close (UpnpWebFileHandle fh)
|
|
{
|
|
struct web_file_t *file = (struct web_file_t *) fh;
|
|
|
|
log_verbose ("http_close\n");
|
|
|
|
if (!file)
|
|
return -1;
|
|
|
|
switch (file->type)
|
|
{
|
|
case web_file_t::FILE_LOCAL:
|
|
close (file->detail.local.fd);
|
|
break;
|
|
case web_file_t::FILE_MEMORY:
|
|
/* no close operation */
|
|
if (file->detail.memory.contents)
|
|
free (file->detail.memory.contents);
|
|
break;
|
|
default:
|
|
log_verbose ("Unknown file type.\n");
|
|
break;
|
|
}
|
|
|
|
if (file->fullpath)
|
|
free (file->fullpath);
|
|
free (file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct UpnpVirtualDirCallbacks virtual_dir_callbacks =
|
|
{
|
|
http_get_info,
|
|
http_open,
|
|
http_read,
|
|
http_write,
|
|
http_seek,
|
|
http_close
|
|
};
|