~mateo-salta/nitroshare/nitroshare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/* NitroShare - A simple network file sharing tool.
   Copyright (C) 2012 Nathan Osman

   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 <QDebug>
#include <QDir>
#include <QFile>
#include <QHostAddress>
#include <QRegExp>
#include <QVariantMap>
#include <qjson/qobjecthelper.h>

#include <dialogs/CAcceptPromptDialog.h>
#include <file/CFileReceiver.h>
#include <util/defaults.h>
#include <util/settings.h>

CFileReceiver::CFileReceiver(QObject * parent)
    : CBasicSocket(parent), m_state(WaitingForHeader), m_files_received(0)
{
    connect(this, SIGNAL(Data(QByteArray)), SLOT(OnData(QByteArray)));
}

CFileReceiver::~CFileReceiver()
{
    qDeleteAll(m_headers);
}

void CFileReceiver::Start(int handle)
{
    setSocketDescriptor(handle);

    qDebug() << "Incoming connection from" << peerAddress().toString();
}

void CFileReceiver::OnData(QByteArray data)
{
    qDebug() << "Received" << data.size() << "bytes from client.";

    /* If we're waiting for the headers then parse them. */
    if(m_state == WaitingForHeader)
    {
        QVariantMap map = DecodeJSON(data);
        ParseHeaders(map);
    }
    else
    {
        /* If there are files remaining to be read, then read the next one. */
        if(m_headers.size())
        {
            CFileHeader * header = m_headers.takeFirst();
            SaveFile(header, data);
            delete header;

            /* Let the client know they can send the next one. */
            SendStatus("continue");
        }

        /* If we have read all of the files, send the ACK back to the server. */
        if(!m_headers.size())
        {
            /* Let the user know we received everything and close
               the connection. */
            if(Settings::Notify("FilesReceived"))
                emit Information(tr("%1 file(s) were received.").arg(m_files_received));

            disconnectFromHost();
        }
    }
}

void CFileReceiver::ParseHeaders(QVariantMap map)
{
    qDebug() << "Parsing headers received from client...";

    /* Ensure that the JSON map contains the name and list of files. */
    if(!map.contains("name") || !map.contains("files"))
    {
        emit Error(tr("Unable to parse the JSON response sent from the client."));
        disconnectFromHost();
    }

    /* Construct the headers for each of the files. */
    m_hostname = map["name"].toString();
    foreach(QVariant variant, map["files"].toList())
    {
        CFileHeader * header = new CFileHeader;
        QJson::QObjectHelper::qvariant2qobject(variant.toMap(), header);
        m_headers.append(header);
    }

    qDebug() << "Processed headers for" << m_headers.size() << "file(s).";

    /* Test the files against the current policy. */
    if(TestAgainstPolicy())
    {
        qDebug() << "Awaiting transfer of first file.";

        /* We are now waiting for the first file to arrive. */
        m_state = WaitingForTransfer;
        SendStatus("continue");
    }
    else
    {
        qDebug() << "Files rejected by current security policy. Aborting transfer.";

        SendStatus("rejected");
        disconnectFromHost();
    }
}

bool CFileReceiver::TestAgainstPolicy()
{
    int policy = Settings::Get("Security/IncomingPolicy").toInt();

    /* If the current policy is to accept all or none, we're done. */
    if(policy == Defaults::AcceptAll)  return true;
    if(policy == Defaults::AcceptNone) return false;

    /* Perhaps the policy is to prompt the user? If so, do it. */
    if(policy == Defaults::AcceptPrompt)
    {
        CAcceptPromptDialog dialog(m_hostname, m_headers);
        return dialog.exec();
    }

    /* Note: we don't perform the RegEx match until we are actually
       ready to perform the transfer since we still need the headers
       for files we do not wish to transfer. */

    return true;
}

bool CFileReceiver::TestReceivedFilesDirectory(QDir dir)
{
    /* Ensure that the directory for the received files exists. */
    if(!dir.exists())
    {
        qDebug() << dir.absolutePath() << "does not exist. Attempting to create it.";

        if(!dir.mkpath(dir.absolutePath()))
        {
            emit Error(tr("Unable to create the directory for received files."));
            return false;
        }
    }

    return true;
}

bool CFileReceiver::TestChecksum(QByteArray & data)
{
    /* First make sure the checksum was actually included. */
    int index = data.indexOf(TerminatingCharacter);
    if(index == -1)
    {
        emit Error(tr("Unable to extract CRC checksum from received file. Skipping file."));
        return false;
    }

    /* Now verify the checksum that was provided and remove it. Generate our own checksum. */
    quint16 orig_crc = data.left(index).toUShort();
    data.remove(0, index + 1);
    quint16 calc_crc = qChecksum(data.data(), data.size());

    qDebug() << "Comparing original CRC checksum" << orig_crc << "to calculated checksum:" << calc_crc;

    if(orig_crc != calc_crc)
    {
        emit Error(tr("CRC integrity check failed. Skipping file."));
        return false;
    }

    return true;
}

void CFileReceiver::SaveFile(CFileHeader * header, QByteArray data)
{
    qDebug() << "Received file" << header->GetFilename() << ".";

    /* Check the security policy to see if we need to filter
       incoming filenames. */
    if(Settings::Get("Security/IncomingPolicy").toInt() == Defaults::AcceptPattern)
    {
        /* If so, perform the regular expression match. */
        QRegExp pattern(Settings::Get("Security/IncomingPattern").toString());
        if(pattern.indexIn(header->GetFilename()) == -1)
        {
            qDebug() << header->GetFilename() << "rejected based on regular expression pattern.";
            return;
        }
    }

    /* Ensure the directory where received files are supposed to go does indeed exist. */
    QDir storage_dir(Settings::Get("General/ReceivedFilesDirectory").toString());
    if(!TestReceivedFilesDirectory(storage_dir))
        return;

    QFile file(storage_dir.absoluteFilePath(header->GetFilename()));
    if(!file.open(QIODevice::WriteOnly))
    {
        emit Error(tr("Unable to store received file '%1'. Skipping file.").arg(header->GetFilename()));
        return;
    }

    /* Extract the CRC checksum if provided. */
    if(header->GetChecksum() && !TestChecksum(data))
        return;

    /* If the file is compressed, we need to uncompress it. */
    if(header->GetCompressed())
    {
        qDebug() << "Uncompressing the contents of the file with zlib.";
        data = qUncompress(data);
    }

    /* Write the data to disk. */
    file.write(data);
    ++m_files_received;

    /* ...and we're done with this file. */
    qDebug() << "File stored on the local filesysten.";
}