/*************************************************************************** * Copyright (C) 2003 by Sylvain Joyeux * * sylvain.joyeux@m4x.org * * The forms are by Willy De la Court * * * * 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 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "apt.h" #include "regexps.h" #include "dpkg.h" #include "parsers/parsers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KIO; /************************************************************************* * Common definitions of HTML fragments */ static const QString html_preamble("\n" "\n"); static const QString html_redirect(html_preamble + QString("\n" "\t\n" "\t\n" "\n" "\n\n" "")); static const QString html_head(html_preamble + QString("\n" "\t\n" "\t\n" "\t%2\n" "\n\n" "\n")); static QString close_html_head(); static QString open_html_head(const QString& title, bool links, AptProtocol const& config) { static const QString html_head_table( "\n" "\n" "\t\n" "\t\n"); QString rowspan; if (links) rowspan = "rowspan=\"2\""; QString ret = html_head .arg(config.stylesheet()) .arg(title) + html_head_table .arg(config.header_background()) .arg(rowspan) .arg(config.logo()) .arg(config.logo_alt()) .arg(title); if (links) { return ret + "\n" "\n" "\t\n"); return format.arg(url).arg(long_desc).arg(name); } static QString close_html_head() { return "\t\n" "\t
\"%4\"%4
\n" "\t\n" "\t\n"; } else { return ret + "\n
\n\n"; } } static QString add_html_head_link(const QString& url, const QString& name, const QString& long_desc) { static const QString format("\t\t
%3
\n" "\t\n" "" ""; } static const QString html_tail("
%1
\n" "\n" ""); QString AptProtocol::make_html_tail(const QString& note, bool with_form) { with_form = m_search && with_form; QString ret; if (with_form) ret = "
\n" + make_html_form(); if (!note.isEmpty()) ret += html_tail.arg(note + ". " + i18n("Page generated by kio_apt.")); else ret += html_tail.arg(i18n("Page generated by kio_apt.")); return ret; } /********************************************************************************** * Search form */ static const QString html_form_begin("\n
\n" "\n"); static const QString html_form_end("\n" "\t\n" "\n" "
\n" "
\n"); static const QString html_form_line("\n" "\t\n" "\t\n" "\n"); static QString make_html_form_line(const QString& type, const QString& label) { return html_form_line.arg(type).arg(label).arg(type).arg(type); } static QString make_extform_cmd(bool ext_form, const KURL& query) { QString cmd = ext_form ? "0" : "1"; QString msg = ext_form ? i18n("Hide extended form") : i18n("Show extended form"); KURL url(query); url.addQueryItem("extended_form", cmd); url.setRef("extformcmd"); return "
\n" "\t[" + msg + "]\n" "
\n"; } /** Prints the HTML code for the query form */ QString AptProtocol::make_html_form() const { bool can_fsearch = false; bool ext_form = KGlobal::config() -> readBoolEntry("extended_form", true); // Only in config-file. Needed for some dpkg-based distros that are not Debian can_fsearch = can_searchfile(true); bool online = false; bool online_form = KGlobal::config() -> readBoolEntry("online_form", true); if (m_adept_batch) online = online_form && (!m_internal) && ext_form && m_adept_batch -> capabilities(PackageManager::ONLINE); QString ret; QTextOStream stream(&ret); stream << make_extform_cmd(ext_form, m_query); if (online) stream << "" << endl; stream << make_html_form_line("search", i18n("Package search")); if (ext_form) { if (can_fsearch) stream << make_html_form_line("fsearch", i18n("File search")); stream << make_html_form_line("show", i18n("Package info")); } stream << html_form_end.arg( i18n("Search") ); if (online) { stream << "\n\n
\n"; stream << html_form_begin; stream << "
" + i18n("Offline search") + "
\n"; stream << m_adept_batch -> getOnlineForm(); stream << "\n
"; } return ret; } /****************************************************************************************/ AptProtocol::AptProtocol( const QCString &pool_socket, const QCString &app_socket ) : SlaveBase( "kio_apt", pool_socket, app_socket ), m_adept_batch(0), m_parser(0) { KStandardDirs* dirs = KGlobal::dirs(); m_stylesheet = dirs->findResource( "data", "kio_apt/kio_apt.css" ); m_logo = dirs->findResource( "data", "kio_apt/" + KGlobal::config() -> readEntryUntranslated("logo", "kdedeb_logo.png" ) ); m_header_background = dirs->findResource( "data", "kio_apt/" + KGlobal::config() -> readEntryUntranslated("background", "headerbg.png" ) ); m_logo_alt = KGlobal::config() -> readEntryUntranslated("alt_tag", i18n("KDE on Debian") ); connect(&m_process, SIGNAL(token(const QString&, const QString&)), this, SLOT(token_dispatch(const QString&, const QString&))); m_adept_batch = new Dpkg(this); if (m_adept_batch) { connect(m_adept_batch, SIGNAL(token(const QString&, const QString&)), this, SLOT(token_dispatch(const QString&, const QString&))); } } AptProtocol::~AptProtocol() {} QString AptProtocol::stylesheet() const { return m_stylesheet; } QString AptProtocol::logo() const { return m_logo; } QString AptProtocol::logo_alt() const { return m_logo_alt; } QString AptProtocol::header_background() const { return m_header_background; } void AptProtocol::token_dispatch(const QString& name, const QString& val) { if (m_parser.get()) (*m_parser)(this, name, val); } void AptProtocol::data(const QCString& string) { using namespace Parsers; (*this) << string; } void AptProtocol::data(const QString& string) { using namespace Parsers; (*this) << string; } void AptProtocol::data(const char* string) { using namespace Parsers; (*this) << string; } void AptProtocol::data(const QByteArray& array) { SlaveBase::data(array); } void AptProtocol::mimetype( const KURL & /*url*/ ) { mimeType( "text/html" ); finished(); } bool AptProtocol::check_validpackage(const QString& query) { static QRegExp rx_pkgname(rxs_pkgname); if (!rx_pkgname.exactMatch(query)) { error( ERR_SLAVE_DEFINED, i18n("\"%1\" is not a valid package name").arg(query) ); return false; } return true; } /******************************************************************** * Main entry point */ static QString read_option(QMap& map, const QString& name, const QString& def) { if (!map.contains(name)) return def; QString ret = map[name]; map.remove(name); return ret; } void AptProtocol::get ( const KURL& url ) { /* The queries have two possible formats : - clean way to call a command apt:/command?query&option=value&option=value&... - needed to simplify forms apt:/?command=query&command2=&command3=&option=value&option=value&... - calls only the query form page apt:/ */ typedef void (AptProtocol::*Command)(const QString&, const QueryOptions&); static const QString commands[] = { "search", "show", "policy", "fsearch", "list", "online", "get", QString::null }; static const Command methods[] = { &AptProtocol::search, &AptProtocol::show, &AptProtocol::policy, &AptProtocol::searchfile, &AptProtocol::listfiles, &AptProtocol::online, &AptProtocol::adept_batch }; QString command, query; Command method = 0; QueryOptions options = url.queryItems(KURL::CaseInsensitiveKeys); // canonize the part before ? : remove the first / QString path = url.path(); QString host = url.host(); if ( path.isEmpty() && !host.isEmpty() ) { path = host; } if (path [0] == '/') path = path.right(path.length() - 1); for (int cmd_idx = 0; !commands[cmd_idx].isNull(); ++cmd_idx) { const QString cmd_it = commands[cmd_idx]; // Look if the command is in the path part if (command.isEmpty() && cmd_it == path) { command = cmd_it; method = methods[cmd_idx]; } if (options.contains(cmd_it)) { if (options[cmd_it].isEmpty() && !options[cmd_it].isNull()) { // we have a &command=& format, we remove it options.remove(cmd_it); } else if (command.isEmpty() && !options[cmd_it].isEmpty()) { // the command is set in the options map command = cmd_it; method = methods[cmd_idx]; query = options[cmd_it]; options.remove(cmd_it); } } } // Well, we have no query for now, let's find it if (query.isEmpty()) { for (QueryOptions::Iterator i = options.begin(); i != options.end(); ++i) { if ((*i).isNull()) { query = KURL::decode_string(i.key()); options.remove(i); break; } } } // Interpret the ioslave config options // and remove them from the options map QString opt = read_option(options, "extended_form", QString::null); if (!opt.isNull()) { bool ext_form = (opt != "0"); KGlobal::config() -> writeEntry("extended_form", ext_form); } // Enable install/remove/upgrade/... opt = read_option(options, "enable_actions", "1"); m_act = (opt != "0"); opt = read_option(options, "enable_search", "1"); m_search = (opt != "0"); // Allow links outside of apt:/ hierarchy opt = read_option(options, "stay_internal", "0"); m_internal = (opt == "1"); // Sync the config (must use kcfg sometime :p) KGlobal::config() -> sync(); if (command.isEmpty() || query.isEmpty()) { //If path isn't empty, then a package is to be installed via an apt:/ link if ( !path.isEmpty()) { query = "install"; options["package"] = path; options["weblinkinstall"] = 1; adept_batch(query, options); return; } // No query or no command, we go in help mode m_query = buildURL(KURL("apt:/")); help(); } else { m_query = buildURL(command, query); m_query.setHTMLRef(url.htmlRef()); for (QueryOptions::ConstIterator i = options.begin(); i != options.end(); ++i) m_query.addQueryItem(i.key(), i.data()); kdDebug() << "Old url " << url << ", new url " << m_query << endl; if (m_query != url) { redirection(m_query); data(QByteArray()); finished(); return; } (this->*method)(query, options); } } /*********************************************************************************** * * form * */ void AptProtocol::help() { mimeType("text/html"); QString buffer; QTextOStream stream(&buffer); stream << open_html_head(i18n("Search Form"), false, *this) << make_html_form() << make_html_tail(QString::null, false); data(buffer); data(QByteArray()); finished(); } /*********************************************************************************** * apt-cache search */ void AptProtocol::search( const QString& query, const QueryOptions& /*options*/ ) { mimeType("text/html"); data(open_html_head(i18n("Package search result for \"%1\"").arg(query), false, *this)); m_parser.reset(new Parsers::Search); (*m_parser)(this, "begin", query); if (!m_process.search( query )) { error(ERR_SLAVE_DEFINED, i18n("Error launching the search").arg(query)); return; } (*m_parser)(this, "end", QString::null); data(make_html_tail( i18n("%1 results").arg(m_parser -> result_count())) ); data(QByteArray()); finished(); } /*********************************************************************************** * apt-cache show */ static QString filelist_cmd(bool show_filelist, const KURL& query) { QString value = show_filelist ? "0" : "1"; QString msg = show_filelist ? i18n("Hide file list") : i18n("Show file list"); KURL url(query); url.addQueryItem("show_filelist", value); url.setRef("filelistcmd"); return ""; } void AptProtocol::show(const QString& package, const QueryOptions& options) { if (!check_validpackage(package)) return; if (options.contains("show_filelist")) { KGlobal::config() -> writeEntry("show_filelist", options["show_filelist"] != "0"); KGlobal::config() -> sync(); } mimeType("text/html"); QString installed_version; /** First, we parse policy * We use here the fact that HTML is generated * during the call of (*policy)(...,"end",...), * since the header changes when the package * is installed or not */ Parsers::Policy* policy = new Parsers::Policy(package, m_act); m_parser.reset(policy); (*m_parser)(this, "begin", QString::null); { if (!m_process.policy( package )) { error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache policy %1\"").arg(package)); return; } installed_version = policy->getInstalled(); bool can_list = can_listfiles(!installed_version.isEmpty()); QString buffer; QTextOStream s(&buffer); if (can_list) { KURL url = buildURL("list", package); s << open_html_head(i18n("Package description for \"%1\"").arg(package), true, *this) << add_html_head_link(url.htmlURL(), i18n("List package files"), "") << close_html_head(); } else { s << open_html_head(i18n("Package description for \"%1\"").arg(package), false, *this); } data(buffer); } (*m_parser)(this, "end", QString::null); /** Add package description section */ m_parser.reset(new Parsers::Show(package, installed_version, m_act)); (*m_parser)(this, "begin", QString::null); { if (!m_process.show(package)) { error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache show %1\"").arg(package)); return; } if (!m_parser -> result_count()) { data("
" + i18n("No package found named \"%1\"").arg(package) + "
\n"); data(make_html_tail()); data(QByteArray()); finished(); return; } } (*m_parser)(this, "end", QString::null); /** Add file list (if enabled) */ bool show_filelist = KGlobal::config() -> readBoolEntry("show_filelist", false); if ( show_filelist ) { if (can_listfiles(!installed_version.isEmpty())) { data( "
\n" + filelist_cmd(show_filelist, m_query) + "
\n"); m_parser.reset(new Parsers::List(!m_internal)); (*m_parser)(this, "begin", QString::null); if (!m_adept_batch -> list(package)) { error(ERR_SLAVE_DEFINED, i18n("Error listing files of %1").arg(package)); return; } (*m_parser)(this, "end", QString::null); data("\n
\n"); } else // cannot list files { data( "
\n" + filelist_cmd(show_filelist, m_query) + "
" + i18n("Cannot list files for non-installed packages") + "
\n"); } } else { data("
\n" + filelist_cmd(show_filelist, m_query)); } data(make_html_tail()); data(QByteArray()); finished(); } /*********************************************************************************** * apt-cache policy */ void AptProtocol::policy( const QString& query, const QueryOptions& /*options*/ ) { if (!check_validpackage(query)) return; mimeType("text/html"); data( open_html_head(i18n("Apt policy for \"%1\"").arg(query), false, *this) ); m_parser.reset(new Parsers::Policy(query, m_act)); (*m_parser)(this, "begin", QString::null); if (!m_process.policy( query )) { error(ERR_SLAVE_DEFINED, i18n("Can't launch the policy for %1").arg(query)); return; } (*m_parser)(this, "end", QString::null); data(make_html_tail()); data(QByteArray()); finished(); } /*********************************************************************************** * Search the package which contains a specific file */ static const QString html_dpkgs_begin("\n\n\n"), html_dpkgs_end("\n\n
\n"); bool AptProtocol::can_searchfile(bool is_installed) const { if (!m_adept_batch) return false; int caps = m_adept_batch -> capabilities(PackageManager::SEARCH_FILE | PackageManager::OFFLINE); if (!caps) return false; return is_installed || !(caps & PackageManager::INSTALLED_ONLY); } void AptProtocol::searchfile(const QString& query, const QueryOptions& /*options*/) { if (!can_searchfile(true)) return; mimeType("text/html"); data( open_html_head(i18n("File search for \"%1\"").arg(query), false, *this) + html_dpkgs_begin ); m_parser.reset(new Parsers::FileSearch); (*m_parser)(this, "begin", QString::null); if (!m_adept_batch -> search( query )) { error(ERR_SLAVE_DEFINED, i18n("Can't launch the package manager").arg(query)); return; } (*m_parser)(this, "end", QString::null); data( html_dpkgs_end + make_html_tail(i18n("%1 files found").arg(m_parser -> result_count())) ); data(QByteArray()); finished(); } /*********************************************************************************** * List the files of a package */ bool AptProtocol::can_listfiles(bool is_installed) const { if (!m_adept_batch) return false; int caps = m_adept_batch -> capabilities(PackageManager::LIST_FILES | PackageManager::OFFLINE); if (!caps) return false; return is_installed || !(caps & PackageManager::INSTALLED_ONLY); } void AptProtocol::listfiles(const QString& query, const QueryOptions& /*options*/) { if (!can_listfiles(true)) return; if (!check_validpackage(query)) return; mimeType("text/html"); KURL ret_url = buildURL("show", query); QString buffer; QTextOStream stream(&buffer); stream << open_html_head(i18n("Files in \"%1\"").arg(query), true, *this) << add_html_head_link(ret_url.htmlURL(), i18n("Show package info"), "") << close_html_head() << endl; data(buffer); m_parser.reset(new Parsers::List(!m_internal)); (*m_parser)(this, "begin", QString::null); if (!m_adept_batch -> list( query )) { error(ERR_SLAVE_DEFINED, i18n("Can't launch the package manager").arg(query)); return; } (*m_parser)(this, "end", QString::null); data(make_html_tail()); data(QByteArray()); finished(); } /*********************************************************************************** * Go use online search services (like packages.debian.org for instance) */ //bool AptProtocol::can_online(int mode) const //{ //// if (!m_adept_batch) return false; //// return m_adept_batch -> capabilities(PackageManager::ONLINE | mode); // return false; //} void AptProtocol::online(const QString& query, const QueryOptions& options) { QString url = m_adept_batch -> getOnlineURL(query, options); redirection(url); finished(); return; } /*********************************************************************************** * Send commands for adept_batch */ void AptProtocol::adept_batch(const QString& query, const QueryOptions& options) { p=NULL; QString command; QString url; QStringList plist; QStringList puninst; QStringList pinst; int pcount; int ip; if (query == "install" || query.isEmpty()) { command = "kdesu adept_batch install "; } else if (query == "remove") { command = "kdesu adept_batch remove "; } if (command.isEmpty()) { error(ERR_SLAVE_DEFINED, i18n("No package manager command specified")); return; } if (!options.contains("package")) { error(ERR_SLAVE_DEFINED, i18n("No package specified")); return; } plist = QStringList::split(", ", options["package"], false); pcount = plist.count(); command += plist.join(" "); if (pcount == 1) { if (query == "install") ip = SlaveBase::messageBox(QuestionYesNo, i18n("Do you want to install %1 ?").arg(plist[0]), i18n("Package Installation")); else ip = SlaveBase::messageBox(QuestionYesNo, i18n("Do you want to remove %1 ?").arg(plist[0]), i18n("Package Removal")); } else { if (query == "install") ip = SlaveBase::messageBox(QuestionYesNo,i18n("Do you want to install the following %1 packages ?\n%2").arg(pcount).arg(options["package"])); else ip = SlaveBase::messageBox(QuestionYesNo,i18n("Do you want to remove the following %1 packages ?\n").arg(pcount).arg(options["package"])); } kdDebug(DEBUG_ZONE) << command << endl; if (ip == KMessageBox::Yes) { p = new KShellProcess; p->clearArguments(); *p << command; p->start( KProcess::Block, KProcess::All ); for(int i = 0; i != pcount; ++i) { QString installed_version; Parsers::Policy* policy = new Parsers::Policy(plist[i], m_act); m_parser.reset(policy); (*m_parser)(this, "begin", QString::null); { if (!m_process.policy( plist[i] )) { error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache policy %1\"").arg(plist[i])); return; } installed_version = policy->getInstalled(); if (installed_version.isEmpty()) { puninst += plist[i]; } else { pinst += plist[i]; } } } if (options.contains("weblinkinstall")) { if (puninst.count() == 0) { messageBox(Information,i18n("Installation successfull.")); } else { QString toto = puninst.join(" "); messageBox(Information,i18n("There was a problem installing %1.").arg(toto)); } return; } else { url = "apt:/show?"; // Outside of a weblink, only one package can be installed at time url += plist[0]; redirection(url); data(QByteArray()); finished(); return; } } else { return; } } KURL AptProtocol::buildURL( const QString & command, const QString & query ) const { KURL url; url.setProtocol("apt"); if (!command.startsWith("/")) url.setPath("/" + command); else url.setPath(command); url.setQuery(query); return buildURL(url); } KURL AptProtocol::buildURL( const KURL& query ) const { KURL url(query); if (!m_act) url.addQueryItem("enable_actions", "0"); if (!m_search) url.addQueryItem("enable_search", "0"); if (m_internal) url.addQueryItem("stay_internal", "1"); return url; } /*********************************************************************************** * * kdemain * */ extern "C" { int kdemain( int argc, char **argv ) { KInstance instance( "kio_apt" ); if ( argc != 4 ) { kdDebug( DEBUG_ZONE ) << "Usage: kio_apt protocol domain-socket1 domain-socket2" << endl; exit ( -1 ); } AptProtocol slave( argv[ 2 ], argv[ 3 ] ); slave.dispatchLoop(); return 0; } } #include "apt.moc"