1
/****************************************************************************
2
** filetrans.cpp - classes for handling file transfers
3
** Copyright (C) 2001, 2002 Justin Karneges
5
** This program is free software; you can redistribute it and/or
6
** modify it under the terms of the GNU General Public License
7
** as published by the Free Software Foundation; either version 2
8
** of the License, or (at your option) any later version.
10
** This program is distributed in the hope that it will be useful,
11
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
** GNU General Public License for more details.
15
** You should have received a copy of the GNU General Public License
16
** along with this program; if not, write to the Free Software
17
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,USA.
19
****************************************************************************/
25
#include<qstringlist.h>
35
static QString urlEncode(const QString &in);
36
static QString urlDecode(const QString &in);
37
static QString mimeGuess(const QString &in);
38
static QString generateHTMLDirectory(bool base, const QString &dir, const QString &title);
41
/****************************************************************************
43
****************************************************************************/
44
FileTransfer::FileTransfer(FileServer *_par)
54
FileTransfer::~FileTransfer()
58
void FileTransfer::discard()
71
void FileTransfer::start()
73
connect(&sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
74
connect(&sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed()));
75
connect(&sock, SIGNAL(bytesWritten(int)), SLOT(sock_bytesWritten(int)));
77
printf("%04d: new transfer: ip=[%s]\n", id, sock.peerAddress().toString().latin1());
80
void FileTransfer::processChunk()
90
// read a chunk from the file
91
readSize = file.readBlock(data.data(), 1024);
99
int left = stringData.size() - bytesSent;
107
for(int n = 0; n < readSize; ++n)
108
data[n] = stringData[bytesSent + n];
111
bytesSent += readSize;
112
printf("\r%04d: sent %d bytes ", id, bytesSent);
114
sock.writeBlock(data.data(), readSize);
117
void FileTransfer::sock_readyRead()
119
printf("%04d: data ready\n", id);
122
int size = sock.readBlock(recvBuf + at, 1024 - at);
130
//printf("got: [%s]\n", recvBuf);
141
for(int n = 0; n < at; ++n) {
142
if(recvBuf[n] == '\n' || recvBuf[n] == '\r') {
151
// see if it is a valid request
152
QString str = recvBuf;
153
printf("%04d: request: [%s]\n", id, str.latin1());
155
QString res, http_ver;
156
if(!getGETInfo(str, &res, &http_ver)) {
161
printf("%04d: res: [%s], http_ver: [%s]\n", id, res.latin1(), http_ver.latin1());
163
char *useImage_data = 0;
164
int useImage_len = 0;
165
if(res == "/image/ft_back.png") {
166
useImage_data = pixdat_ft_back;
167
useImage_len = pixlen_ft_back;
169
else if(res == "/image/ft_file.png") {
170
useImage_data = pixdat_ft_file;
171
useImage_len = pixlen_ft_file;
173
else if(res == "/image/ft_folder.png") {
174
useImage_data = pixdat_ft_folder;
175
useImage_len = pixlen_ft_folder;
180
QString resource, key;
181
if(!extractResourceInfo(res, &resource, &key)) {
186
// does the key pass?
187
FileServerItem *fi = par->getFSI(key);
193
printf("%04d: validated: key=[%s]\n", id, key.latin1());
195
// how about the resource?
198
int transType = FT_FILE;
201
if(resource != fi->file.fileName()) {
205
fname = fi->file.filePath();
210
int n = resource.find('/');
216
base = resource.mid(0, n);
217
rest = resource.mid(n);
220
if(base != fi->file.fileName()) {
225
fname = fi->file.filePath() + rest;
234
QString str = d.canonicalPath();
241
fname += QString("/") + info.fileName();
245
QString basePath = fi->file.filePath();
246
int len = basePath.length();
247
for(n = 0; n < len; ++n) {
248
if(basePath.at(n) != fname.at(n))
252
printf("%04d: illegal path. hacking attempt?\n", id);
258
transType = FT_STRING;
263
if(transType == FT_FILE) {
264
printf("%04d: serving: [%s]\n", id, info.filePath().latin1());
268
// now try to open it
270
if(!file.open(IO_ReadOnly)) {
283
"Content-Length: %d\n"
284
"Connection: close\n"
285
"\n", mimeGuess(info.fileName()).latin1(), info.size());
286
printf("Sending header:\n%s", header.latin1());
287
sock.writeBlock(header, header.length());
290
printf("%04d: listing: [%s]\n", id, info.filePath().latin1());
293
if(fi->file.filePath() == info.filePath())
297
stringData = generateHTMLDirectory(base, info.filePath(), QString("Index of %1").arg(resource)).utf8();
303
QString header = QString(
305
"Content-Type: text/html\n"
306
"Content-Length: %1\n"
307
"Connection: close\n"
308
"\n").arg(stringData.size());
309
printf("Sending header:\n%s", header.latin1());
310
sock.writeBlock(header, header.length());
314
printf("%04d: serving: [%s]\n", id, res.latin1());
317
stringData.resize(useImage_len);
318
memcpy(stringData.data(), useImage_data, useImage_len);
324
QString header = QString(
326
"Content-Type: image/png\n"
327
"Content-Length: %1\n"
328
"Connection: close\n"
329
"\n").arg(stringData.size());
330
printf("Sending header:\n%s", header.latin1());
331
sock.writeBlock(header, header.length());
335
// write the first chunk
341
void FileTransfer::sock_connectionClosed()
343
printf("\n%04d: connection closed\n", id);
347
void FileTransfer::sock_bytesWritten(int)
349
if(sock.bytesToWrite() <= 0) {
351
printf("\n%04d: transfer finished.\n", id);
357
QTimer::singleShot(0, this, SLOT(processChunk()));
361
bool FileTransfer::getGETInfo(const QString &getReq, QString *resource, QString *http_ver)
363
QString req = getReq.simplifyWhiteSpace();
365
// these must be the first 4 chars
366
if(req.left(4) != "GET ")
369
QString str = getReq.mid(4);
371
// is there a space later? if so, grab what is after it (assuming HTTP version)
373
int n = str.find(' ');
375
if((int)str.length() > n + 1)
376
*http_ver = str.mid(n + 1);
384
bool FileTransfer::extractResourceInfo(const QString &resource, QString *fname, QString *key)
386
QString str = resource;
388
// make sure we begin with a slash
395
// make sure there is a slash to separate key and filename
396
int n = str.find('/');
400
*key = str.mid(0, n);
403
fname->remove(0,1); // remove the beginning slash
404
*fname = urlDecode(*fname);
409
QString urlEncode(const QString &in)
413
for(unsigned int n = 0; n < in.length(); ++n) {
414
if(in.at(n) == '.') {
417
else if(in.at(n) == ' ') {
420
else if(!in.at(n).isLetterOrNumber()) {
423
hex.sprintf("%%%02X", in.at(n).latin1());
427
out.append(in.at(n));
434
QString urlDecode(const QString &in)
438
for(unsigned int n = 0; n < in.length(); ++n) {
439
if(in.at(n) == '%' && (in.length() - n - 1) >= 2) {
440
QString str = in.mid(n+1,2);
442
char c = str.toInt(&ok, 16);
450
else if(in.at(n) == '+')
453
out.append(in.at(n));
459
QString mimeGuess(const QString &in)
463
for(n = in.length()-1; n > 0; --n) {
470
ext = in.mid(n+1).lower();
472
if(ext == "txt" || ext == "c" || ext == "h" || ext == "cpp")
474
else if(ext == "gif")
476
else if(ext == "jpeg" || ext == "jpg" || ext == "jpe")
478
else if(ext == "png")
481
return "application/octet-stream";
484
QString generateHTMLDirectory(bool base, const QString &dir, const QString &title)
489
// blatant sourceforge ripoff
493
" <title>%1</title>\n"
494
" <style type=\"text/css\">\n"
496
//" background-image: url(img/sf-stipple.png);\n"
497
" background-color: #aaa;\n"
500
" td { font: small sans-serif; padding-left: 0.5em; padding-right: 0.5em;}\n"
501
" .footer {text-align: center;}\n"
502
" .directory {color: #595;}\n"
503
" .sort {color: #000;}\n"
504
" .label-date {font-family: monospace; color: #555; text-align: left;}\n"
505
" .label-size {font-family: monospace; color: #555; text-align: center;}\n"
511
"<table align=\"center\" width=\"90%\" bgcolor=\"#FFFFFF\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
512
" <tr><td align=\"center\">\n"
513
" <table width=\"100%\" cellpadding=\"3\" cellspacing=\"1\" bgcolor=\"#FFFFFF\">\n"
514
" <tr bgcolor=\"#FFFFFF\">\n<td>Psi Folder Share</td></tr>\n"
515
//" <tr bgcolor=\"#FFFFFF\">\n"
516
//" <td colspan=\"3\">Current Directory: <b>http://telia.dl.sourceforge.net/ mirrors/ </b></td>\n"
518
" <tr bgcolor=\"#FFFFFF\">\n"
519
" <th width=\"80%\">File Name</th>\n"
520
" <th width=\"10%\">Size</th>\n"
521
" <th width=\"10%\">Date</th>\n"
525
QStringList entries = d.entryList();
527
bool colorOn = FALSE;
528
for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) {
533
if(fname == ".." && base)
538
str += "<tr bgcolor=\"#DDDDDD\">\n";
542
str += "<tr bgcolor=\"#EEEEEE\">\n";
546
QFileInfo info(dir + '/' + fname);
551
str += QString("<td nowrap align=\"left\"><img src=\"%1\" align=\"middle\" width=\"20\" height=\"22\"> <a href=\"../\"><font class=\"directory\"><b>-- Parent Directory --</b></font></a></td>\n").arg("/image/ft_back.png");
553
str += QString("<td nowrap align=\"left\"><img src=\"%1\" align=\"middle\" width=\"20\" height=\"22\"> ").arg(info.isDir() ? "/image/ft_folder.png" : "/image/ft_file.png") + QString("<a href=\"%1\">").arg(fname) + QString("<font class=\"directory\"><b>%1</b></font></a></td>\n").arg(fname);
559
size.sprintf("%d", info.size());
561
date = info.lastModified().toString();
564
" <td nowrap align=\"center\" class=\"label-size\">%1</td>\n").arg(size);
566
" <td nowrap align=\"left\" class=\"label-date\">%1</td>\n"
567
"</tr>\n").arg(date);
574
" <td colspan=\"3\">\n"
579
" <p class=\"footer\">%1</p>\n"
585
"</html>\n\n").arg(QDateTime::currentDateTime().toString());
591
/****************************************************************************
593
****************************************************************************/
594
FileServer::FileServer()
598
old_sigpipe = signal(SIGPIPE, SIG_IGN);
601
// seed random number generator
605
sigmap[0] = new QSignalMapper(this);
606
sigmap[1] = new QSignalMapper(this);
607
connect(sigmap[0], SIGNAL(mapped(int)), this, SLOT(transferDiscarded(int)));
608
filelist.setAutoDelete(TRUE);
615
FileServer::~FileServer()
620
ftlist.setAutoDelete(TRUE);
624
signal(SIGPIPE, old_sigpipe);
628
void FileServer::setServer(const QString &_host, int _port)
636
FileServerItem *FileServer::addFile(const QString &_fname, const QString &desc, const QString &who)
638
QString fname = _fname;
640
// hack off a trailing backslash
641
if(fname.at(fname.length()-1) == '/')
642
fname.remove(fname.length()-1, 1);
651
QPtrListIterator<FileServerItem> it(filelist);
653
for(; (fi = it.current()); ++it) {
661
QFileInfo info(fname);
667
i = new FileServerItem;
669
i->type = info.isDir() ? 1: 0;
673
i->url = QString("http://") + host + QString(":%1").arg(port) + "/" + key + "/" + urlEncode(i->file.fileName());
681
QPtrList<FileServerItem> FileServer::items() const
683
QPtrList<FileServerItem> list;
684
list.setAutoDelete(FALSE);
686
QPtrListIterator<FileServerItem> it(filelist);
688
for(; (fi = it.current()); ++it)
694
void FileServer::listen()
701
serv = new ServSock(port);
702
connect(serv, SIGNAL(connectionReady(int)), SLOT(connectionReady(int)));
705
FileTransfer *FileServer::findByID(int id)
707
QPtrListIterator<FileTransfer> it(ftlist);
708
for(FileTransfer *f; (f = it.current()); ++it) {
716
void FileServer::connectionReady(int sock)
718
FileTransfer *f = new FileTransfer(this);
719
f->sock.setSocket(sock);
724
sigmap[0]->setMapping(f, f->id);
725
connect(f, SIGNAL(discarding()), sigmap[0], SLOT(map()));
729
QString FileServer::genKey()
733
for(int i = 0; i < 4; ++i) {
734
int word = rand() & 0xffff;
735
for(int n = 0; n < 4; ++n) {
737
s.sprintf("%x", (word >> (n * 4)) & 0xf);
745
FileServerItem *FileServer::getFSI(const QString &key)
747
QPtrListIterator<FileServerItem> it(filelist);
749
for(; (fi = it.current()); ++it) {
757
void FileServer::transferDiscarded(int x)
759
FileTransfer *f = findByID(x);
763
printf("%04d: transfer object discarded.\n", f->id);
768
// QGuardedPtr<FileTransfer> gf = f;
771
/****************************************************************************
773
****************************************************************************/
774
ServSock::ServSock(int port)
775
:QServerSocket(port, 16)
779
void ServSock::newConnection(int x)