aboutsummaryrefslogtreecommitdiff
path: root/src/protocols/gopherclient.cpp
blob: a55ae0ca9140fe49969bcccf047758804420b374 (plain) (blame)
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
#include "gopherclient.hpp"
#include "ioutil.hpp"
#include "kristall.hpp"

GopherClient::GopherClient(QObject *parent) : ProtocolHandler(parent)
{
    connect(&socket, &QTcpSocket::connected, this, &GopherClient::on_connected);
    connect(&socket, &QTcpSocket::readyRead, this, &GopherClient::on_readRead);
    connect(&socket, &QTcpSocket::disconnected, this, &GopherClient::on_finished);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
    connect(&socket, &QTcpSocket::errorOccurred, this, &GopherClient::on_socketError);
#else
    connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &GopherClient::on_socketError);
#endif

    connect(&socket, &QAbstractSocket::hostFound, this, [this]() {
        emit this->requestStateChange(RequestState::HostFound);
    });
    emit this->requestStateChange(RequestState::None);
}

GopherClient::~GopherClient()
{

}

bool GopherClient::supportsScheme(const QString &scheme) const
{
    return (scheme == "gopher");
}

bool GopherClient::startRequest(const QUrl &url, RequestOptions options)
{
    Q_UNUSED(options)

    if(isInProgress())
        return false;

    if(url.scheme() != "gopher")
        return false;

    emit this->requestStateChange(RequestState::Started);

    // Second char on the URL path denotes the Gopher type
    // See https://tools.ietf.org/html/rfc4266
    QString type = url.path().mid(1, 1);

    mime = "application/octet-stream";
    if(type == "") mime = "text/gophermap";
    else if(type == "0") mime = "text/plain";
    else if(type == "1") mime = "text/gophermap";
    else if(type == "g") mime = "image/gif";
    else if(type == "I") mime = "image/unknown";
    else if(type == "h") mime = "text/html";
    else if(type == "s") mime = "audio/unknown";

    is_processing_binary = (type == "5") or (type == "9") or (type == "I") or (type == "g");

    this->requested_url = url;
    this->was_cancelled = false;
    socket.connectToHost(url.host(), url.port(70));

    return true;
}

bool GopherClient::isInProgress() const
{
    return socket.isOpen();
}

bool GopherClient::cancelRequest()
{
    was_cancelled = true;
    if (socket.state() != QTcpSocket::UnconnectedState)
    {
        socket.disconnectFromHost();
        socket.waitForDisconnected(1500);
    }
    socket.close();
    body.clear();
    return true;
}

void GopherClient::on_connected()
{
    auto blob = (requested_url.path().mid(2) + "\r\n").toUtf8();

    IoUtil::writeAll(socket, blob);

    emit this->requestStateChange(RequestState::Connected);
}

void GopherClient::on_readRead()
{
    body.append(socket.readAll());

    if(not is_processing_binary) {
        // Strip the "lone dot" from gopher data
        if(int index = body.indexOf("\r\n.\r\n"); index >= 0) {
            body.resize(index + 2);
            socket.close();
        }
    }

    if(not was_cancelled) {
        emit this->requestProgress(body.size());
    }
}

void GopherClient::on_finished()
{
    if(not was_cancelled)
    {
        this->on_readRead();
        emit this->requestComplete(this->body, mime);
        was_cancelled = true;
    }
    body.clear();

    emit this->requestStateChange(RequestState::None);
}

void GopherClient::on_socketError(QAbstractSocket::SocketError error_code)
{
    // When remote host closes session, the client closes the socket.
    // This is more sane then erroring out here as it's a perfectly legal
    // state and we know the connection has ended.
    if (error_code == QAbstractSocket::RemoteHostClosedError) {
        return;
    } else {
        this->emitNetworkError(error_code, socket.errorString());
    }
}