File indexing completed on 2024-04-21 04:36:03

0001 /*
0002  * XDebug Debugger Support
0003  *
0004  * Copyright (C) 2004-2006 by Thiago Silva <thiago.silva@kdemail.net>
0005  * Copyright 2009 Niko Sams <niko.sams@gmail.com>
0006  *
0007  * This program is free software; you can redistribute it and/or modify
0008  * it under the terms of the GNU General Public License as
0009  * published by the Free Software Foundation; either version 2 of the
0010  * License, or (at your option) any later version.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU General Public
0018  * License along with this program; if not, write to the
0019  * Free Software Foundation, Inc.,
0020  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0021  */
0022 
0023 #include "connection.h"
0024 
0025 #include <QTcpSocket>
0026 #include <QTextCodec>
0027 #include <QDomElement>
0028 
0029 #include <QDebug>
0030 #include <QUrl>
0031 
0032 #include <interfaces/icore.h>
0033 #include <interfaces/idebugcontroller.h>
0034 
0035 #include "debugsession.h"
0036 #include "debuggerdebug.h"
0037 
0038 namespace XDebug {
0039 Connection::Connection(QTcpSocket* socket, QObject* parent)
0040     : QObject(parent)
0041     , m_socket(socket)
0042     , m_currentState(DebugSession::NotStartedState)
0043     , m_lastTransactionId(0)
0044 {
0045     Q_ASSERT(m_socket);
0046     m_socket->setParent(this);
0047 
0048     m_codec = QTextCodec::codecForLocale();
0049 
0050     connect(m_socket,&QTcpSocket::disconnected, this, &Connection::closed);
0051     connect(m_socket,  static_cast<void (QTcpSocket::*) (QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &Connection::error);
0052 
0053     connect(m_socket, &QTcpSocket::readyRead, this, &Connection::readyRead);
0054 }
0055 
0056 Connection::~Connection()
0057 {
0058 }
0059 
0060 void Connection::close()
0061 {
0062     delete m_socket;
0063     m_socket = nullptr;
0064 }
0065 
0066 void Connection::error(QAbstractSocket::SocketError error)
0067 {
0068     //qCWarning(KDEV_PHP_DEBUGGER) << m_socket->errorString();
0069     qCWarning(KDEV_PHP_DEBUGGER) << error;
0070 }
0071 
0072 void Connection::readyRead()
0073 {
0074     while (m_socket && m_socket->bytesAvailable()) {
0075         long length;
0076         {
0077             QByteArray data;
0078             char c;
0079             while (m_socket->getChar(&c)) {
0080                 if (c == 0) {
0081                     break;
0082                 }
0083                 data.append(c);
0084             }
0085             length = data.toLong();
0086         }
0087         QByteArray data;
0088         while (data.length() <= length) {
0089             if (!data.isEmpty() && !m_socket->waitForReadyRead()) {
0090                 return;
0091             }
0092             data += m_socket->read(length - data.length() + 1);
0093         }
0094         //qCDebug(KDEV_PHP_DEBUGGER) << data;
0095 
0096         QDomDocument doc;
0097         doc.setContent(data);
0098         if (doc.documentElement().tagName() == "init") {
0099             processInit(doc);
0100         } else if (doc.documentElement().tagName() == "response") {
0101             processResponse(doc);
0102         } else if (doc.documentElement().tagName() == "stream") {
0103             processStream(doc);
0104         } else {
0105             //qCWarning(KDEV_PHP_DEBUGGER) << "unknown element" << xml->name();
0106         }
0107     }
0108 }
0109 void Connection::sendCommand(const QString& cmd, QStringList arguments, const QByteArray& data, CallbackBase* callback)
0110 {
0111     Q_ASSERT(m_socket);
0112     Q_ASSERT(m_socket->isOpen());
0113 
0114     arguments.prepend("-i " + QString::number(++m_lastTransactionId));
0115     if (callback) {
0116         m_callbacks[m_lastTransactionId] = callback;
0117     }
0118 
0119     QByteArray out = m_codec->fromUnicode(cmd);
0120     if (!arguments.isEmpty()) {
0121         out += ' ' + m_codec->fromUnicode(arguments.join(QString(' ')));
0122     }
0123     if (!data.isEmpty()) {
0124         out += " -- " + data.toBase64();
0125     }
0126     qCDebug(KDEV_PHP_DEBUGGER) << out;
0127     m_socket->write(out);
0128     m_socket->write("\0", 1);
0129 }
0130 
0131 void Connection::processInit(const QDomDocument& xml)
0132 {
0133     qCDebug(KDEV_PHP_DEBUGGER) << "idekey" << xml.documentElement().attribute("idekey");
0134 
0135     sendCommand("feature_get -n encoding");
0136     sendCommand("stderr -c 1"); //copy stderr to IDE
0137     sendCommand("stdout -c 1"); //copy stdout to IDE
0138 
0139     setState(DebugSession::StartingState);
0140 }
0141 
0142 void Connection::processResponse(const QDomDocument& xml)
0143 {
0144     QString status = xml.documentElement().attribute("status");
0145     if (status == "running") {
0146         setState(DebugSession::ActiveState);
0147     } else if (status == "stopping") {
0148         setState(DebugSession::StoppingState);
0149     } else if (status == "stopped") {
0150         setState(DebugSession::StoppedState);
0151     } else if (status == "break") {
0152         setState(DebugSession::PausedState);
0153         QDomElement el = xml.documentElement().firstChildElement();
0154         if (el.nodeName() == "xdebug:message") {
0155             QUrl file = QUrl(el.attribute("filename"));
0156             int lineNum = el.attribute("lineno").toInt() - 1;
0157             emit currentPositionChanged(file, lineNum);
0158         }
0159     }
0160     if (xml.documentElement().attribute("command") == "feature_get" && xml.documentElement().attribute("feature_name") == "encoding") {
0161         QTextCodec* c = QTextCodec::codecForName(xml.documentElement().text().toUtf8());
0162         if (c) {
0163             m_codec = c;
0164         }
0165     }
0166 
0167     CallbackBase* callback = nullptr;
0168     if (xml.documentElement().hasAttribute("transaction_id")) {
0169         int transactionId = xml.documentElement().attribute("transaction_id").toInt();
0170         if (m_callbacks.contains(transactionId)) {
0171             callback = m_callbacks[transactionId];
0172             m_callbacks.remove(transactionId);
0173         }
0174     }
0175     if (callback && !callback->allowError()) {
0176         //if callback doesn't handle errors himself
0177         QDomElement el = xml.documentElement().firstChildElement();
0178         if (el.nodeName() == "error") {
0179             qCWarning(KDEV_PHP_DEBUGGER) << "error" << el.attribute("code") << "for transaction" << xml.documentElement().attribute("transaction_id");
0180             qCDebug(KDEV_PHP_DEBUGGER) << el.firstChildElement().text();
0181             Q_ASSERT(false);
0182         }
0183     }
0184     if (callback) {
0185         callback->execute(xml);
0186         delete callback;
0187     }
0188 }
0189 
0190 void Connection::setState(DebugSession::DebuggerState state)
0191 {
0192     qCDebug(KDEV_PHP_DEBUGGER) << state;
0193     if (m_currentState == state) {
0194         return;
0195     }
0196     m_currentState = state;
0197     emit stateChanged(state);
0198 }
0199 
0200 DebugSession::DebuggerState Connection::currentState()
0201 {
0202     return m_currentState;
0203 }
0204 
0205 void Connection::processStream(const QDomDocument& xml)
0206 {
0207     if (xml.documentElement().attribute("encoding") == "base64") {
0208         /* We ignore the output type for now
0209            KDevelop::IRunProvider::OutputTypes outputType;
0210            if (xml->attributes().value("type") == "stdout") {
0211             outputType = KDevelop::IRunProvider::StandardOutput;
0212            } else if (xml->attributes().value("type") == "stderr") {
0213             outputType = KDevelop::IRunProvider::StandardError;
0214            } else {
0215             qCWarning(KDEV_PHP_DEBUGGER) << "unknown output type" << xml->attributes().value("type");
0216             return;
0217            }
0218          */
0219 
0220         QString c = m_codec->toUnicode(QByteArray::fromBase64(xml.documentElement().text().toUtf8()));
0221         //qCDebug(KDEV_PHP_DEBUGGER) << c;
0222         emit output(c);
0223         m_outputLine += c;
0224         int pos = m_outputLine.indexOf('\n');
0225         if (pos != -1) {
0226             emit outputLine(m_outputLine.left(pos));
0227             m_outputLine = m_outputLine.mid(pos + 1);
0228         }
0229     } else {
0230         qCWarning(KDEV_PHP_DEBUGGER) << "unknown encoding" << xml.documentElement().attribute("encoding");
0231     }
0232 }
0233 
0234 void Connection::processFinished(int exitCode)
0235 {
0236     Q_UNUSED(exitCode);
0237     /*
0238        QMapIterator<KDevelop::IRunProvider::OutputTypes, QString> i(m_outputLine);
0239        while (i.hasNext()) {
0240         i.next();
0241         emit outputLine(i.value(), i.key());
0242        }
0243      */
0244 }
0245 
0246 QTcpSocket* Connection::socket()
0247 {
0248     return m_socket;
0249 }
0250 }