File indexing completed on 2024-04-28 08:27:08
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 }