1141 lines
31 KiB
C++
1141 lines
31 KiB
C++
/*
|
|
* File: file.c :)
|
|
*
|
|
* Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>
|
|
* Copyright (C) 2024 Rodrigo Arias Mallo <rodarima@gmail.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.
|
|
*/
|
|
|
|
/*
|
|
* Directory scanning is no longer streamed, but it gets sorted instead!
|
|
* Directory entries on top, files next.
|
|
* With new HTML layout.
|
|
*/
|
|
|
|
#include <ctype.h> /* for isspace */
|
|
#include <errno.h> /* for errno */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/un.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "../dpip/dpip.hh"
|
|
#include "dpiutil.hh"
|
|
#include "d_size.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <compare>
|
|
#include <algorithm>
|
|
|
|
/*
|
|
* Debugging macros
|
|
*/
|
|
#define _MSG(...)
|
|
#define MSG(...) printf("[file dpi]: " __VA_ARGS__)
|
|
#define _MSG_RAW(...)
|
|
#define MSG_RAW(...) printf(__VA_ARGS__)
|
|
|
|
|
|
#define MAXNAMESIZE 30
|
|
#define HIDE_DOTFILES TRUE
|
|
|
|
/*
|
|
* Communication flags
|
|
*/
|
|
#define FILE_AUTH_OK 1 /* Authentication done */
|
|
#define FILE_READ 2 /* Waiting data */
|
|
#define FILE_WRITE 4 /* Sending data */
|
|
#define FILE_DONE 8 /* Operation done */
|
|
#define FILE_ERR 16 /* Operation error */
|
|
|
|
|
|
typedef enum {
|
|
st_start = 10,
|
|
st_dpip,
|
|
st_http,
|
|
st_content,
|
|
st_done,
|
|
st_err
|
|
} FileState;
|
|
|
|
struct FileInfo {
|
|
std::string full_path;
|
|
const char *filename;
|
|
off_t size;
|
|
mode_t mode;
|
|
time_t mtime;
|
|
|
|
std::strong_ordering operator <=> ( const FileInfo & ) const;
|
|
};
|
|
|
|
struct DilloDir {
|
|
std::string dirname;
|
|
std::vector< std::unique_ptr< FileInfo > > flist; /* List of files and subdirectories (for sorting) */
|
|
};
|
|
|
|
struct ClientInfo {
|
|
Dsh *sh;
|
|
char *orig_url;
|
|
char *filename;
|
|
int file_fd;
|
|
off_t file_sz;
|
|
DilloDir *d_dir;
|
|
FileState state;
|
|
int err_code;
|
|
int flags;
|
|
int old_style;
|
|
};
|
|
|
|
/*
|
|
* Forward references
|
|
*/
|
|
static const char *File_content_type(const char *filename);
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
static int DPIBYE = 0;
|
|
static int OLD_STYLE = 0;
|
|
/* A list for the clients we are serving */
|
|
static Dlist *Clients;
|
|
/* Set of filedescriptors we're working on */
|
|
fd_set read_set, write_set;
|
|
|
|
|
|
/*
|
|
* Close a file descriptor, but handling EINTR
|
|
*/
|
|
static void File_close(int fd)
|
|
{
|
|
while (fd >= 0 && close(fd) < 0 && errno == EINTR)
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Detects 'Content-Type' when the server does not supply one.
|
|
* It uses the magic(5) logic from file(1). Currently, it
|
|
* only checks the few mime types that Dillo supports.
|
|
*
|
|
* 'Data' is a pointer to the first bytes of the raw data.
|
|
* (this is based on a_Misc_get_content_type_from_data())
|
|
*/
|
|
static const char *File_get_content_type_from_data(void *Data, size_t Size)
|
|
{
|
|
static const char *Types[] = {
|
|
"application/octet-stream",
|
|
"text/html", "text/plain",
|
|
"image/gif", "image/png", "image/jpeg",
|
|
};
|
|
int Type = 0;
|
|
char *p = reinterpret_cast< char * >( Data );
|
|
size_t i, non_ascci;
|
|
|
|
_MSG("File_get_content_type_from_data:: Size = %zu\n", Size);
|
|
|
|
/* HTML try */
|
|
for (i = 0; i < Size && dIsspace(p[i]); ++i);
|
|
if ((Size - i >= 5 && !dStrnAsciiCasecmp(p+i, "<html", 5)) ||
|
|
(Size - i >= 5 && !dStrnAsciiCasecmp(p+i, "<head", 5)) ||
|
|
(Size - i >= 6 && !dStrnAsciiCasecmp(p+i, "<title", 6)) ||
|
|
(Size - i >= 14 && !dStrnAsciiCasecmp(p+i, "<!doctype html", 14)) ||
|
|
/* this line is workaround for FTP through the Squid proxy */
|
|
(Size - i >= 17 && !dStrnAsciiCasecmp(p+i, "<!-- HTML listing", 17))) {
|
|
|
|
Type = 1;
|
|
|
|
/* Images */
|
|
} else if (Size >= 4 && !strncmp(p, "GIF8", 4)) {
|
|
Type = 3;
|
|
} else if (Size >= 4 && !strncmp(p, "\x89PNG", 4)) {
|
|
Type = 4;
|
|
} else if (Size >= 2 && !strncmp(p, "\xff\xd8", 2)) {
|
|
/* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking
|
|
* at the character representation should be machine independent. */
|
|
Type = 5;
|
|
|
|
/* Text */
|
|
} else {
|
|
/* We'll assume "text/plain" if the set of chars above 127 is <= 10
|
|
* in a 256-bytes sample. Better heuristics are welcomed! :-) */
|
|
non_ascci = 0;
|
|
Size = MIN (Size, 256);
|
|
for (i = 0; i < Size; i++)
|
|
if ((uchar_t) p[i] > 127)
|
|
++non_ascci;
|
|
if (Size == 256) {
|
|
Type = (non_ascci > 10) ? 0 : 2;
|
|
} else {
|
|
Type = (non_ascci > 0) ? 0 : 2;
|
|
}
|
|
}
|
|
|
|
return (Types[Type]);
|
|
}
|
|
|
|
/*
|
|
* Compare two FileInfo pointers
|
|
* This function is used for sorting directories
|
|
*/
|
|
static int File_comp(const FileInfo *f1, const FileInfo *f2)
|
|
{
|
|
if (S_ISDIR(f1->mode)) {
|
|
if (S_ISDIR(f2->mode)) {
|
|
return strcmp(f1->filename, f2->filename);
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (S_ISDIR(f2->mode)) {
|
|
return 1;
|
|
} else {
|
|
return strcmp(f1->filename, f2->filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::strong_ordering
|
|
FileInfo::operator <=> ( const FileInfo &rhs ) const
|
|
{
|
|
const auto &lhs= *this;
|
|
const auto result= File_comp( &lhs, &rhs );
|
|
|
|
if( result < 0 ) return std::strong_ordering::less;
|
|
if( result > 0 ) return std::strong_ordering::greater;
|
|
return std::strong_ordering::equal;
|
|
}
|
|
|
|
/*
|
|
* Allocate a DilloDir structure, set safe values in it and sort the entries.
|
|
*/
|
|
static std::unique_ptr< DilloDir > File_dillodir_new(const std::string &dirname)
|
|
{
|
|
struct stat sb;
|
|
struct dirent *de;
|
|
DIR *dir;
|
|
std::unique_ptr< FileInfo > fifo;
|
|
std::string fname;
|
|
int dirname_len;
|
|
|
|
if (!(dir = opendir(dirname.c_str())))
|
|
return NULL;
|
|
|
|
auto Ddir = std::make_unique< DilloDir >();
|
|
Ddir->dirname = dirname;
|
|
|
|
dirname_len = Ddir->dirname.size();
|
|
|
|
/* Scan every name and sort them */
|
|
while ((de = readdir(dir)) != 0) {
|
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
|
continue; /* skip "." and ".." */
|
|
|
|
if (HIDE_DOTFILES) {
|
|
/* Don't add hidden files or backup files to the list */
|
|
if (de->d_name[0] == '.' ||
|
|
de->d_name[0] == '#' ||
|
|
(de->d_name[0] != '\0' &&
|
|
de->d_name[strlen(de->d_name) - 1] == '~'))
|
|
continue;
|
|
}
|
|
|
|
fname = Ddir->dirname + de->d_name;
|
|
if (stat(fname.c_str(), &sb) == -1) {
|
|
continue; /* ignore files we can't stat */
|
|
}
|
|
|
|
auto finfo = std::make_unique< FileInfo >();
|
|
finfo->full_path = fname;
|
|
finfo->filename = finfo->full_path.c_str() + dirname_len;
|
|
finfo->size = sb.st_size;
|
|
finfo->mode = sb.st_mode;
|
|
finfo->mtime = sb.st_mtime;
|
|
|
|
Ddir->flist.push_back( std::move( finfo ) );
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
/* sort the entries */
|
|
std::sort( begin( Ddir->flist ), end( Ddir->flist ),
|
|
[]( const auto &lhs, const auto &rhs ) { return *lhs < *rhs; } );
|
|
|
|
return Ddir;
|
|
}
|
|
|
|
/*
|
|
* Deallocate a DilloDir structure.
|
|
*/
|
|
static void File_dillodir_free(DilloDir *Ddir)
|
|
{
|
|
delete Ddir;
|
|
}
|
|
|
|
/*
|
|
* Output the string for parent directory
|
|
*/
|
|
static void File_print_parent_dir(ClientInfo *client, const char *dirname)
|
|
{
|
|
if (strcmp(dirname, "/") != 0) { /* Not the root dir */
|
|
char *p, *parent, *HUparent, *Uparent;
|
|
|
|
parent = dStrdup(dirname);
|
|
/* cut trailing slash */
|
|
parent[strlen(parent) - 1] = '\0';
|
|
/* make 'parent' have the parent dir path */
|
|
if ((p = strrchr(parent, '/')))
|
|
*(p + 1) = '\0';
|
|
|
|
Uparent = dStrdup( Escape_uri_str(parent, NULL).c_str() );
|
|
HUparent = Escape_html_str(Uparent);
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"<a href='file:%s'>Parent directory</a>", HUparent);
|
|
dFree(HUparent);
|
|
dFree(Uparent);
|
|
dFree(parent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Given a timestamp, output an HTML-formatted date string.
|
|
*/
|
|
static void File_print_mtime(ClientInfo *client, time_t mtime)
|
|
{
|
|
char *ds = ctime(&mtime);
|
|
|
|
/* Month, day and {hour or year} */
|
|
if (client->old_style) {
|
|
a_Dpip_dsh_printf(client->sh, 0, " %.3s %.2s", ds + 4, ds + 8);
|
|
if (time(NULL) - mtime > 15811200) {
|
|
a_Dpip_dsh_printf(client->sh, 0, " %.4s", ds + 20);
|
|
} else {
|
|
a_Dpip_dsh_printf(client->sh, 0, " %.5s", ds + 11);
|
|
}
|
|
} else {
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"<td>%.3s %.2s %.5s", ds + 4, ds + 8,
|
|
/* (more than 6 months old) ? year : hour; */
|
|
(time(NULL) - mtime > 15811200) ? ds + 20 : ds + 11);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a HTML-line from file info.
|
|
*/
|
|
static void File_info2html(ClientInfo *client, FileInfo *finfo, int n)
|
|
{
|
|
int size;
|
|
const char *sizeunits;
|
|
char namebuf[MAXNAMESIZE + 1];
|
|
char *HUref, *Hname;
|
|
const char *ref, *filecont, *name = finfo->filename;
|
|
|
|
if (finfo->size <= 9999) {
|
|
size = finfo->size;
|
|
sizeunits = "bytes";
|
|
} else if (finfo->size / 1024 <= 9999) {
|
|
size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2);
|
|
sizeunits = "KB";
|
|
} else {
|
|
size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2);
|
|
sizeunits = "MB";
|
|
}
|
|
|
|
/* we could note if it's a symlink... */
|
|
if (S_ISDIR (finfo->mode)) {
|
|
filecont = "Directory";
|
|
} else if (finfo->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
|
|
filecont = "Executable";
|
|
} else {
|
|
filecont = File_content_type(finfo->full_path.c_str());
|
|
if (!filecont || !strcmp(filecont, "application/octet-stream"))
|
|
filecont = "unknown";
|
|
}
|
|
|
|
ref = name;
|
|
|
|
if (strlen(name) > MAXNAMESIZE) {
|
|
memcpy(namebuf, name, MAXNAMESIZE - 3);
|
|
strcpy(namebuf + (MAXNAMESIZE - 3), "...");
|
|
name = namebuf;
|
|
}
|
|
|
|
/* escape problematic filenames */
|
|
const std::string Uref = Escape_uri_str(ref, NULL);
|
|
HUref = Escape_html_str(Uref.c_str());
|
|
Hname = Escape_html_str(name);
|
|
|
|
if (client->old_style) {
|
|
const char *dots = ".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..";
|
|
int ndots = MAXNAMESIZE - strlen(name);
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"%s<a href='%s'>%s</a>"
|
|
" %s"
|
|
" %-11s%4d %-5s",
|
|
S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname,
|
|
dots + 50 - (ndots > 0 ? ndots : 0),
|
|
filecont, size, sizeunits);
|
|
|
|
} else {
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"<tr align=center %s><td>%s<td align=left><a href='%s'>%s</a>"
|
|
"<td>%s<td>%d %s",
|
|
(n & 1) ? "bgcolor=#dcdcdc" : "",
|
|
S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname,
|
|
filecont, size, sizeunits);
|
|
}
|
|
File_print_mtime(client, finfo->mtime);
|
|
a_Dpip_dsh_write_str(client->sh, 0, "\n");
|
|
|
|
dFree(Hname);
|
|
dFree(HUref);
|
|
}
|
|
|
|
/*
|
|
* Send the HTML directory page in HTTP.
|
|
*/
|
|
static void File_send_dir(ClientInfo *client)
|
|
{
|
|
int n;
|
|
char *d_cmd, *Hdirname, *HUdirname;
|
|
DilloDir *Ddir = client->d_dir;
|
|
|
|
if (client->state == st_start) {
|
|
/* Send DPI command */
|
|
d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
|
|
client->orig_url);
|
|
a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
|
|
dFree(d_cmd);
|
|
client->state = st_dpip;
|
|
|
|
} else if (client->state == st_dpip) {
|
|
/* send HTTP header and HTML top part */
|
|
|
|
/* Send page title */
|
|
auto Udirname = Escape_uri_str(Ddir->dirname.c_str(), NULL);
|
|
HUdirname = Escape_html_str(Udirname.c_str());
|
|
Hdirname = Escape_html_str(Ddir->dirname.c_str());
|
|
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html\r\n"
|
|
"\r\n"
|
|
"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n"
|
|
"<HTML>\n<HEAD>\n <BASE href='file:%s'>\n"
|
|
" <TITLE>file:%s</TITLE>\n</HEAD>\n"
|
|
"<BODY><H1>Directory listing of %s</H1>\n",
|
|
HUdirname, Hdirname, Hdirname);
|
|
dFree(Hdirname);
|
|
dFree(HUdirname);
|
|
|
|
if (client->old_style) {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "<pre>\n");
|
|
}
|
|
|
|
/* Output the parent directory */
|
|
File_print_parent_dir(client, Ddir->dirname.c_str());
|
|
|
|
/* HTML style toggle */
|
|
a_Dpip_dsh_write_str(client->sh, 0,
|
|
" <a href='dpi:/file/toggle'>%</a>\n");
|
|
|
|
if (not Ddir->flist.empty()) {
|
|
if (client->old_style) {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "\n\n");
|
|
} else {
|
|
a_Dpip_dsh_write_str(client->sh, 0,
|
|
"<br><br>\n"
|
|
"<table border=0 cellpadding=1 cellspacing=0"
|
|
" bgcolor=#E0E0E0 width=100%>\n"
|
|
"<tr align=center>\n"
|
|
"<td>\n"
|
|
"<td width=60%><b>Filename</b>"
|
|
"<td><b>Type</b>"
|
|
"<td><b>Size</b>"
|
|
"<td><b>Modified at</b>\n");
|
|
}
|
|
} else {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "<br><br>Directory is empty...");
|
|
}
|
|
client->state = st_http;
|
|
|
|
} else if (client->state == st_http) {
|
|
/* send directories as HTML contents */
|
|
for (n = 0; n < Ddir->flist.size(); ++n) {
|
|
File_info2html(client, Ddir->flist.at( n ).get(), n+1);
|
|
}
|
|
|
|
if (client->old_style) {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "</pre>\n");
|
|
} else if (not Ddir->flist.empty()) {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "</table>\n");
|
|
}
|
|
|
|
a_Dpip_dsh_write_str(client->sh, 1, "</BODY></HTML>\n");
|
|
client->state = st_content;
|
|
client->flags |= FILE_DONE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a content type based on the extension of the filename.
|
|
*/
|
|
static const char *File_ext(const char *filename)
|
|
{
|
|
char *e;
|
|
|
|
if (!(e = strrchr(const_cast< char * >( filename ), '.')))
|
|
return NULL;
|
|
|
|
e++;
|
|
|
|
if (!dStrAsciiCasecmp(e, "gif")) {
|
|
return "image/gif";
|
|
} else if (!dStrAsciiCasecmp(e, "jpg") ||
|
|
!dStrAsciiCasecmp(e, "jpeg")) {
|
|
return "image/jpeg";
|
|
} else if (!dStrAsciiCasecmp(e, "png")) {
|
|
return "image/png";
|
|
} else if (!dStrAsciiCasecmp(e, "svg")) {
|
|
return "image/svg+xml";
|
|
} else if (!dStrAsciiCasecmp(e, "html") ||
|
|
!dStrAsciiCasecmp(e, "xhtml") ||
|
|
!dStrAsciiCasecmp(e, "htm") ||
|
|
!dStrAsciiCasecmp(e, "shtml")) {
|
|
return "text/html";
|
|
} else if (!dStrAsciiCasecmp(e, "txt")) {
|
|
return "text/plain";
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Based on the extension, return the content_type for the file.
|
|
* (if there's no extension, analyze the data and try to figure it out)
|
|
*/
|
|
static const char *File_content_type(const char *filename)
|
|
{
|
|
int fd;
|
|
struct stat sb;
|
|
const char *ct;
|
|
char buf[256];
|
|
ssize_t buf_size;
|
|
|
|
if (!(ct = File_ext(filename))) {
|
|
/* everything failed, let's analyze the data... */
|
|
if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) != -1) {
|
|
if ((buf_size = read(fd, buf, 256)) == 256 ) {
|
|
ct = File_get_content_type_from_data(buf, (size_t)buf_size);
|
|
|
|
} else if (stat(filename, &sb) != -1 &&
|
|
buf_size > 0 && buf_size == sb.st_size) {
|
|
ct = File_get_content_type_from_data(buf, (size_t)buf_size);
|
|
}
|
|
File_close(fd);
|
|
}
|
|
}
|
|
_MSG("File_content_type: name=%s ct=%s\n", filename, ct);
|
|
return ct;
|
|
}
|
|
|
|
/*
|
|
* Send an error page
|
|
*/
|
|
static void File_prepare_send_error_page(ClientInfo *client, int res,
|
|
const char *orig_url)
|
|
{
|
|
client->state = st_err;
|
|
client->err_code = res;
|
|
client->orig_url = dStrdup(orig_url);
|
|
client->flags &= ~FILE_READ;
|
|
client->flags |= FILE_WRITE;
|
|
}
|
|
|
|
/*
|
|
* Send an error page
|
|
*/
|
|
static void File_send_error_page(ClientInfo *client)
|
|
{
|
|
const char *status;
|
|
char *d_cmd;
|
|
Dstr *body = dStr_sized_new(128);
|
|
|
|
if (client->err_code == EACCES) {
|
|
status = "403 Forbidden";
|
|
} else if (client->err_code == ENOENT) {
|
|
status = "404 Not Found";
|
|
} else {
|
|
/* good enough */
|
|
status = "500 Internal Server Error";
|
|
}
|
|
dStr_append(body, status);
|
|
dStr_append(body, "\n");
|
|
dStr_append(body, dStrerror(client->err_code));
|
|
|
|
/* Send DPI command */
|
|
d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
|
|
client->orig_url);
|
|
a_Dpip_dsh_write_str(client->sh, 0, d_cmd);
|
|
dFree(d_cmd);
|
|
|
|
a_Dpip_dsh_printf(client->sh, 0,
|
|
"HTTP/1.1 %s\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"\r\n"
|
|
"%s",
|
|
status, body->len, body->str);
|
|
dStr_free(body, TRUE);
|
|
|
|
client->flags |= FILE_DONE;
|
|
}
|
|
|
|
/*
|
|
* Scan the directory, sort and prepare to send it enclosed in HTTP.
|
|
*/
|
|
static int File_prepare_send_dir(ClientInfo *client,
|
|
const char *DirName, const char *orig_url)
|
|
{
|
|
std::string ds_dirname;
|
|
DilloDir *Ddir;
|
|
|
|
/* Let's make sure this directory url has a trailing slash */
|
|
ds_dirname = DirName;
|
|
if (ds_dirname[ds_dirname.size() - 1] != '/')
|
|
ds_dirname+= "/";
|
|
|
|
/* Let's get a structure ready for transfer */
|
|
Ddir = File_dillodir_new(ds_dirname.c_str()).release();
|
|
if (Ddir) {
|
|
/* looks ok, set things accordingly */
|
|
client->orig_url = dStrdup(orig_url);
|
|
client->d_dir = Ddir;
|
|
client->state = st_start;
|
|
client->flags &= ~FILE_READ;
|
|
client->flags |= FILE_WRITE;
|
|
return 0;
|
|
} else
|
|
return EACCES;
|
|
}
|
|
|
|
/*
|
|
* Prepare to send HTTP headers and then the file itself.
|
|
*/
|
|
static int File_prepare_send_file(ClientInfo *client,
|
|
const char *filename,
|
|
const char *orig_url)
|
|
{
|
|
int fd, res = -1;
|
|
struct stat sb;
|
|
|
|
if (stat(filename, &sb) != 0) {
|
|
/* prepare a file-not-found error */
|
|
res = ENOENT;
|
|
} else if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) {
|
|
/* prepare an error message */
|
|
res = errno;
|
|
} else {
|
|
/* looks ok, set things accordingly */
|
|
client->file_fd = fd;
|
|
client->file_sz = sb.st_size;
|
|
client->d_dir = NULL;
|
|
client->state = st_start;
|
|
client->filename = dStrdup(filename);
|
|
client->orig_url = dStrdup(orig_url);
|
|
client->flags &= ~FILE_READ;
|
|
client->flags |= FILE_WRITE;
|
|
res = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Try to stat the file and determine if it's readable.
|
|
*/
|
|
static void File_get(ClientInfo *client, const char *filename,
|
|
const char *orig_url)
|
|
{
|
|
int res;
|
|
struct stat sb;
|
|
|
|
if (stat(filename, &sb) != 0) {
|
|
/* stat failed, prepare a file-not-found error. */
|
|
res = ENOENT;
|
|
} else if (S_ISDIR(sb.st_mode)) {
|
|
/* set up for reading directory */
|
|
res = File_prepare_send_dir(client, filename, orig_url);
|
|
} else {
|
|
/* set up for reading a file */
|
|
res = File_prepare_send_file(client, filename, orig_url);
|
|
}
|
|
if (res != 0) {
|
|
File_prepare_send_error_page(client, res, orig_url);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send HTTP headers and then the file itself.
|
|
*/
|
|
static int File_send_file(ClientInfo *client)
|
|
{
|
|
//#define LBUF 1
|
|
#define LBUF 16*1024
|
|
|
|
const char *ct;
|
|
const char *unknown_type = "application/octet-stream";
|
|
char buf[LBUF], *d_cmd, *name;
|
|
int st, st2, namelen;
|
|
bool gzipped = false;
|
|
|
|
if (client->state == st_start) {
|
|
/* Send DPI command */
|
|
d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
|
|
client->orig_url);
|
|
a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
|
|
dFree(d_cmd);
|
|
client->state = st_dpip;
|
|
|
|
} else if (client->state == st_dpip) {
|
|
/* send HTTP header */
|
|
|
|
/* Check for gzipped file */
|
|
namelen = strlen(client->filename);
|
|
if (namelen > 3 &&
|
|
!dStrAsciiCasecmp(client->filename + namelen - 3, ".gz")) {
|
|
gzipped = true;
|
|
namelen -= 3;
|
|
}
|
|
/* Content-Type info is based on filename extension (with ".gz" removed).
|
|
* If there's no known extension, perform data sniffing.
|
|
* If this doesn't lead to a conclusion, use "application/octet-stream".
|
|
*/
|
|
name = dStrndup(client->filename, namelen);
|
|
if (!(ct = File_content_type(name)))
|
|
ct = unknown_type;
|
|
dFree(name);
|
|
|
|
/* Send HTTP headers */
|
|
a_Dpip_dsh_write_str(client->sh, 0, "HTTP/1.1 200 OK\r\n");
|
|
if (gzipped) {
|
|
a_Dpip_dsh_write_str(client->sh, 0, "Content-Encoding: gzip\r\n");
|
|
}
|
|
if (!gzipped || strcmp(ct, unknown_type)) {
|
|
a_Dpip_dsh_printf(client->sh, 0, "Content-Type: %s\r\n", ct);
|
|
} else {
|
|
/* If we don't know type for gzipped data, let dillo figure it out. */
|
|
}
|
|
a_Dpip_dsh_printf(client->sh, 1,
|
|
"Content-Length: %ld\r\n"
|
|
"\r\n",
|
|
client->file_sz);
|
|
client->state = st_http;
|
|
|
|
} else if (client->state == st_http) {
|
|
/* Send body -- raw file contents */
|
|
if ((st = a_Dpip_dsh_tryflush(client->sh)) < 0) {
|
|
client->flags |= (st == -3) ? FILE_ERR : 0;
|
|
} else {
|
|
/* no pending data, let's send new data */
|
|
do {
|
|
st2 = read(client->file_fd, buf, LBUF);
|
|
} while (st2 < 0 && errno == EINTR);
|
|
if (st2 < 0) {
|
|
MSG("\nERROR while reading from file '%s': %s\n\n",
|
|
client->filename, dStrerror(errno));
|
|
client->flags |= FILE_ERR;
|
|
} else if (st2 == 0) {
|
|
client->state = st_content;
|
|
client->flags |= FILE_DONE;
|
|
} else {
|
|
/* ok to write */
|
|
st = a_Dpip_dsh_trywrite(client->sh, buf, st2);
|
|
client->flags |= (st == -3) ? FILE_ERR : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Given a hex octet (e3, 2F, 20), return the corresponding
|
|
* character if the octet is valid, and -1 otherwise
|
|
*/
|
|
static int File_parse_hex_octet(const char *s)
|
|
{
|
|
int hex_value;
|
|
char *tail, hex[3];
|
|
|
|
if ((hex[0] = s[0]) && (hex[1] = s[1])) {
|
|
hex[2] = 0;
|
|
hex_value = strtol(hex, &tail, 16);
|
|
if (tail - hex == 2)
|
|
return hex_value;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Make a file URL into a human (and machine) readable path.
|
|
* The tilde '~' character is expanded to the value of the user home.
|
|
* The idea is to always have a path that starts with only one slash.
|
|
* Embedded slashes are ignored.
|
|
*/
|
|
static char *File_normalize_path(const char *orig)
|
|
{
|
|
char *str = (char *) orig, *basename = NULL, *ret = NULL, *p;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
/* Make sure the string starts with "file:" */
|
|
if (dStrnAsciiCasecmp(str, "file:", 5))
|
|
return ret;
|
|
|
|
str += 5; /* skip "file:" */
|
|
|
|
char *tmp = NULL;
|
|
|
|
if (str[0] == '~' && (str[1] == '/' || str[1] == '\0')) {
|
|
/* Expand home tilde "~" into "/home/userxyz" */
|
|
const char *home = dGethomedir();
|
|
/* Add separator if needed */
|
|
const char *sep = home[strlen(home) - 1] == '/' ? "" : "/";
|
|
char *next = str + 1;
|
|
while (*next == '/')
|
|
next++;
|
|
str = tmp = dStrconcat(home, sep, next, NULL);
|
|
} else if (dStrnAsciiCasecmp(str, "//localhost/", 12) == 0) {
|
|
/* Skip "//localhost" */
|
|
str += 11;
|
|
}
|
|
|
|
/* Skip packed slashes, and leave just one */
|
|
while (str[0] == '/' && str[1] == '/')
|
|
str++;
|
|
|
|
{
|
|
int i, val;
|
|
Dstr *ds = dStr_sized_new(32);
|
|
dStr_sprintf(ds, "%s%s%s",
|
|
basename ? basename : "",
|
|
basename ? "/" : "",
|
|
str);
|
|
dFree(basename);
|
|
if (tmp)
|
|
dFree(tmp);
|
|
|
|
/* Parse possible hexadecimal octets in the URI path */
|
|
for (i = 0; ds->str[i]; ++i) {
|
|
if (ds->str[i] == '%' &&
|
|
(val = File_parse_hex_octet(ds->str + i+1)) != -1) {
|
|
ds->str[i] = val;
|
|
dStr_erase(ds, i+1, 2);
|
|
}
|
|
}
|
|
/* Remove the fragment if not a part of the filename */
|
|
if ((p = strrchr(ds->str, '#')) != NULL && access(ds->str, F_OK) != 0)
|
|
dStr_truncate(ds, p - ds->str);
|
|
ret = ds->str;
|
|
dStr_free(ds, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set the style flag and ask for a reload, so it shows immediately.
|
|
*/
|
|
static void File_toggle_html_style(ClientInfo *client)
|
|
{
|
|
char *d_cmd;
|
|
|
|
OLD_STYLE = !OLD_STYLE;
|
|
d_cmd = a_Dpip_build_cmd("cmd=%s", "reload_request");
|
|
a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
|
|
dFree(d_cmd);
|
|
}
|
|
|
|
/*
|
|
* Perform any necessary cleanups upon abnormal termination
|
|
*/
|
|
static void termination_handler(int signum)
|
|
{
|
|
MSG("\nexit(signum), signum=%d\n\n", signum);
|
|
exit(signum);
|
|
}
|
|
|
|
|
|
/* Client handling ----------------------------------------------------------*/
|
|
|
|
/*
|
|
* Add a new client to the list.
|
|
*/
|
|
static ClientInfo *File_add_client(int sock_fd)
|
|
{
|
|
ClientInfo *new_client;
|
|
|
|
new_client = new ClientInfo;
|
|
new_client->sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);
|
|
new_client->orig_url = NULL;
|
|
new_client->filename = NULL;
|
|
new_client->file_fd = -1;
|
|
new_client->file_sz = 0;
|
|
new_client->d_dir = NULL;
|
|
new_client->state = FileState( 0 ); // reinterpret_cast
|
|
new_client->err_code = 0;
|
|
new_client->flags = FILE_READ;
|
|
new_client->old_style = OLD_STYLE;
|
|
|
|
dList_append(Clients, new_client);
|
|
|
|
return new_client;
|
|
}
|
|
|
|
/*
|
|
* Remove a client from the list.
|
|
*/
|
|
static void File_remove_client(ClientInfo *client)
|
|
{
|
|
dList_remove(Clients, (void *)client);
|
|
|
|
_MSG("Closing Socket Handler\n");
|
|
a_Dpip_dsh_close(client->sh);
|
|
a_Dpip_dsh_free(client->sh);
|
|
File_close(client->file_fd);
|
|
dFree(client->orig_url);
|
|
dFree(client->filename);
|
|
File_dillodir_free(client->d_dir);
|
|
|
|
dFree(client);
|
|
}
|
|
|
|
/*
|
|
* Serve this client.
|
|
*/
|
|
static void File_serve_client(void *data, int f_write)
|
|
{
|
|
char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *path;
|
|
ClientInfo *client = reinterpret_cast< ClientInfo * >( data );
|
|
int st;
|
|
|
|
while (1) {
|
|
_MSG("File_serve_client %p, flags=%d state=%d\n",
|
|
client, client->flags, client->state);
|
|
if (client->flags & (FILE_DONE | FILE_ERR))
|
|
break;
|
|
if (client->flags & FILE_READ) {
|
|
dpip_tag = a_Dpip_dsh_read_token(client->sh, 0);
|
|
_MSG("dpip_tag={%s}\n", dpip_tag);
|
|
if (!dpip_tag)
|
|
break;
|
|
}
|
|
|
|
if (client->flags & FILE_READ) {
|
|
if (!(client->flags & FILE_AUTH_OK)) {
|
|
/* Authenticate our client... */
|
|
st = a_Dpip_check_auth(dpip_tag);
|
|
_MSG("a_Dpip_check_auth returned %d\n", st);
|
|
client->flags |= (st == 1) ? FILE_AUTH_OK : FILE_ERR;
|
|
} else {
|
|
/* Get file request */
|
|
cmd = a_Dpip_get_attr(dpip_tag, "cmd");
|
|
url = a_Dpip_get_attr(dpip_tag, "url");
|
|
path = File_normalize_path(url);
|
|
if (cmd) {
|
|
if (strcmp(cmd, "DpiBye") == 0) {
|
|
DPIBYE = 1;
|
|
MSG("(pid %d): Got DpiBye.\n", (int)getpid());
|
|
client->flags |= FILE_DONE;
|
|
} else if (url && dStrnAsciiCasecmp(url, "dpi:", 4) == 0 &&
|
|
strcmp(url+4, "/file/toggle") == 0) {
|
|
File_toggle_html_style(client);
|
|
} else if (path) {
|
|
File_get(client, path, url);
|
|
} else {
|
|
client->flags |= FILE_ERR;
|
|
MSG("ERROR: URL was %s\n", url);
|
|
}
|
|
}
|
|
dFree(path);
|
|
dFree(url);
|
|
dFree(cmd);
|
|
dFree(dpip_tag);
|
|
break;
|
|
}
|
|
dFree(dpip_tag);
|
|
|
|
} else if (f_write) {
|
|
/* send our answer */
|
|
if (client->state == st_err)
|
|
File_send_error_page(client);
|
|
else if (client->d_dir)
|
|
File_send_dir(client);
|
|
else
|
|
File_send_file(client);
|
|
break;
|
|
}
|
|
} /*while*/
|
|
|
|
client->flags |= (client->sh->status & DPIP_ERROR) ? FILE_ERR : 0;
|
|
client->flags |= (client->sh->status & DPIP_EOF) ? FILE_DONE : 0;
|
|
}
|
|
|
|
/*
|
|
* Serve the client queue.
|
|
*/
|
|
static void File_serve_clients(void)
|
|
{
|
|
int i, f_read, f_write;
|
|
ClientInfo *client;
|
|
|
|
for (i = 0; (client = reinterpret_cast< ClientInfo * >( dList_nth_data(Clients, i) )); ++i) {
|
|
f_read = FD_ISSET(client->sh->fd_in, &read_set);
|
|
f_write = FD_ISSET(client->sh->fd_out, &write_set);
|
|
if (!f_read && !f_write)
|
|
continue;
|
|
File_serve_client(client, f_write);
|
|
if (client->flags & (FILE_DONE | FILE_ERR)) {
|
|
File_remove_client(client);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* Check the fd sets for activity, with a max timeout.
|
|
* return value: 0 if timeout, 1 if input available, -1 if error.
|
|
*/
|
|
static int File_check_fds(uint_t seconds)
|
|
{
|
|
int i, st;
|
|
ClientInfo *client;
|
|
struct timeval timeout;
|
|
|
|
/* initialize observed file descriptors */
|
|
FD_ZERO (&read_set);
|
|
FD_ZERO (&write_set);
|
|
FD_SET (STDIN_FILENO, &read_set);
|
|
for (i = 0; (client = reinterpret_cast< ClientInfo * >( dList_nth_data(Clients, i) )); ++i) {
|
|
if (client->flags & FILE_READ)
|
|
FD_SET (client->sh->fd_in, &read_set);
|
|
if (client->flags & FILE_WRITE)
|
|
FD_SET (client->sh->fd_out, &write_set);
|
|
}
|
|
_MSG("Watching %d fds\n", dList_length(Clients) + 1);
|
|
|
|
/* Initialize the timeout data structure. */
|
|
timeout.tv_sec = seconds;
|
|
timeout.tv_usec = 0;
|
|
|
|
do {
|
|
st = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout);
|
|
} while (st == -1 && errno == EINTR);
|
|
/*
|
|
MSG_RAW(" (%d%s%s)", STDIN_FILENO,
|
|
FD_ISSET(STDIN_FILENO, &read_set) ? "R" : "",
|
|
FD_ISSET(STDIN_FILENO, &write_set) ? "W" : "");
|
|
for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {
|
|
MSG_RAW(" (%d%s%s)", client->sh->fd_in,
|
|
FD_ISSET(client->sh->fd_in, &read_set) ? "R" : "",
|
|
FD_ISSET(client->sh->fd_out, &write_set) ? "W" : "");
|
|
}
|
|
MSG_RAW("\n");
|
|
*/
|
|
return st;
|
|
}
|
|
|
|
|
|
int main(void)
|
|
{
|
|
struct sockaddr_in sin;
|
|
socklen_t sin_sz;
|
|
int sock_fd, c_st, st = 1;
|
|
|
|
/* Arrange the cleanup function for abnormal terminations */
|
|
if (signal (SIGINT, termination_handler) == SIG_IGN)
|
|
signal (SIGINT, SIG_IGN);
|
|
if (signal (SIGHUP, termination_handler) == SIG_IGN)
|
|
signal (SIGHUP, SIG_IGN);
|
|
if (signal (SIGTERM, termination_handler) == SIG_IGN)
|
|
signal (SIGTERM, SIG_IGN);
|
|
|
|
MSG("(v.2) accepting connections...\n");
|
|
//sleep(20);
|
|
|
|
/* initialize observed file descriptors */
|
|
FD_ZERO (&read_set);
|
|
FD_ZERO (&write_set);
|
|
FD_SET (STDIN_FILENO, &read_set);
|
|
|
|
/* Set STDIN socket nonblocking (to ensure accept() never blocks) */
|
|
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL));
|
|
|
|
/* initialize Clients list */
|
|
Clients = dList_new(512);
|
|
|
|
/* some OSes may need this... */
|
|
sin_sz = sizeof(sin);
|
|
|
|
/* start the service loop */
|
|
while (!DPIBYE) {
|
|
/* wait for activity */
|
|
do {
|
|
c_st = File_check_fds(10);
|
|
} while (c_st == 0 && !DPIBYE);
|
|
if (c_st < 0) {
|
|
MSG(" select() %s\n", dStrerror(errno));
|
|
break;
|
|
}
|
|
if (DPIBYE)
|
|
break;
|
|
|
|
if (FD_ISSET(STDIN_FILENO, &read_set)) {
|
|
/* accept the incoming connection */
|
|
do {
|
|
sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &sin_sz);
|
|
} while (sock_fd < 0 && errno == EINTR);
|
|
if (sock_fd == -1) {
|
|
if (errno == EAGAIN)
|
|
continue;
|
|
MSG(" accept() %s\n", dStrerror(errno));
|
|
break;
|
|
} else {
|
|
_MSG(" accept() fd=%d\n", sock_fd);
|
|
/* Set nonblocking */
|
|
fcntl(sock_fd, F_SETFL, O_NONBLOCK | fcntl(sock_fd, F_GETFL));
|
|
/* Create and initialize a new client */
|
|
File_add_client(sock_fd);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
File_serve_clients();
|
|
}
|
|
|
|
if (DPIBYE)
|
|
st = 0;
|
|
return st;
|
|
}
|
|
|