/* Copyright (C) 2005 J.F.Dockes
* 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.
*
* 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, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "autoconfig.h"
#include <sys/types.h>
#include <time.h>
#include "safesyswait.h"
#include <list>
#include "cstr.h"
#include "execmd.h"
#include "mh_exec.h"
#include "mh_html.h"
#include "log.h"
#include "cancelcheck.h"
#include "smallut.h"
#include "md5ut.h"
#include "rclconfig.h"
using namespace std;
MEAdv::MEAdv(int maxsecs)
: m_filtermaxseconds(maxsecs)
{
m_start = time(0L);
}
void MEAdv::reset()
{
m_start = time(0L);
}
void MEAdv::newData(int n)
{
LOGDEB2("MHExec:newData(" << n << ")\n");
if (m_filtermaxseconds > 0 &&
time(0L) - m_start > m_filtermaxseconds) {
LOGERR("MimeHandlerExec: filter timeout (" << m_filtermaxseconds <<
" S)\n");
throw HandlerTimeout();
}
// If a cancel request was set by the signal handler (or by us
// just above), this will raise an exception. Another approach
// would be to call ExeCmd::setCancel().
CancelCheck::instance().checkCancel();
}
MimeHandlerExec::MimeHandlerExec(RclConfig *cnf, const std::string& id)
: RecollFilter(cnf, id), missingHelper(false), m_filtermaxseconds(900),
m_filtermaxmbytes(0), m_handlernomd5(false), m_hnomd5init(false),
m_nomd5(false)
{
m_config->getConfParam("filtermaxseconds", &m_filtermaxseconds);
m_config->getConfParam("filtermaxmbytes", &m_filtermaxmbytes);
}
bool MimeHandlerExec::set_document_file_impl(const std::string& mt,
const std::string &file_path)
{
// Can't do this in constructor as script name not set yet. Do it
// once on first call
unordered_set<string> nomd5tps;
bool tpsread(false);
if (false == m_hnomd5init) {
m_hnomd5init = true;
if (m_config->getConfParam("nomd5types", &nomd5tps)) {
tpsread = true;
if (!nomd5tps.empty()) {
if (params.size() &&
nomd5tps.find(path_getsimple(params[0])) !=
nomd5tps.end()) {
m_handlernomd5 = true;
}
// On windows the 1st param is often a script interp
// name (e.g. "python", and the script name is 2nd
if (params.size() > 1 &&
nomd5tps.find(path_getsimple(params[1])) !=
nomd5tps.end()) {
m_handlernomd5 = true;
}
}
}
}
m_nomd5 = m_handlernomd5;
if (!m_nomd5) {
// Check for MIME type based md5 suppression
if (!tpsread) {
m_config->getConfParam("nomd5types", &nomd5tps);
}
if (nomd5tps.find(mt) != nomd5tps.end()) {
m_nomd5 = true;
}
}
m_fn = file_path;
m_havedoc = true;
return true;
}
bool MimeHandlerExec::skip_to_document(const string& ipath)
{
LOGDEB("MimeHandlerExec:skip_to_document: [" << ipath << "]\n");
m_ipath = ipath;
return true;
}
// Execute an external program to translate a file from its native
// format to text or html.
bool MimeHandlerExec::next_document()
{
if (m_havedoc == false)
return false;
m_havedoc = false;
if (missingHelper) {
LOGDEB("MimeHandlerExec::next_document(): helper known missing\n");
return false;
}
if (params.empty()) {
// Hu ho
LOGERR("MimeHandlerExec::next_document: empty params\n");
m_reason = "RECFILTERROR BADCONFIG";
return false;
}
// Command name
string cmd = params.front();
// Build parameter vector: delete cmd name and add the file name
vector<string>myparams(params.begin() + 1, params.end());
myparams.push_back(m_fn);
if (!m_ipath.empty())
myparams.push_back(m_ipath);
// Execute command, store the output
string& output = m_metaData[cstr_dj_keycontent];
output.erase();
ExecCmd mexec;
MEAdv adv(m_filtermaxseconds);
mexec.setAdvise(&adv);
mexec.putenv("RECOLL_CONFDIR", m_config->getConfDir());
mexec.putenv(m_forPreview ? "RECOLL_FILTER_FORPREVIEW=yes" :
"RECOLL_FILTER_FORPREVIEW=no");
mexec.setrlimit_as(m_filtermaxmbytes);
int status;
try {
status = mexec.doexec(cmd, myparams, 0, &output);
} catch (HandlerTimeout) {
LOGERR("MimeHandlerExec: handler timeout\n" );
status = 0x110f;
} catch (CancelExcept) {
LOGERR("MimeHandlerExec: cancelled\n" );
status = 0x110f;
}
if (status) {
LOGERR("MimeHandlerExec: command status 0x" << status << " for " <<
cmd << "\n");
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
// That's how execmd signals a failed exec (most probably
// a missing command). Let'hope no filter uses the same value as
// an exit status... Disable myself permanently and signal the
// missing cmd.
missingHelper = true;
m_reason = string("RECFILTERROR HELPERNOTFOUND ") + cmd;
} else if (output.find("RECFILTERROR") == 0) {
// If the output string begins with RECFILTERROR, then it's
// interpretable error information out from a recoll script
m_reason = output;
list<string> lerr;
stringToStrings(output, lerr);
if (lerr.size() > 2) {
list<string>::iterator it = lerr.begin();
it++;
if (*it == "HELPERNOTFOUND") {
// No use trying again and again to execute this filter,
// it won't work.
missingHelper = true;
}
}
}
return false;
}
finaldetails();
return true;
}
void MimeHandlerExec::handle_cs(const string& mt, const string& icharset)
{
string charset(icharset);
// cfgFilterOutputCharset comes from the mimeconf filter
// definition line and defaults to UTF-8 if empty. If the value is
// "default", we use the default input charset value defined in
// recoll.conf (which may vary depending on directory)
if (charset.empty()) {
charset = cfgFilterOutputCharset.empty() ? cstr_utf8 :
cfgFilterOutputCharset;
if (!stringlowercmp("default", charset)) {
charset = m_dfltInputCharset;
}
}
m_metaData[cstr_dj_keyorigcharset] = charset;
// If this is text/plain transcode_to/check utf-8
if (!mt.compare(cstr_textplain)) {
(void)txtdcode("mh_exec/m");
} else {
m_metaData[cstr_dj_keycharset] = charset;
}
}
void MimeHandlerExec::finaldetails()
{
// The default output mime type is html, but it may be defined
// otherwise in the filter definition.
m_metaData[cstr_dj_keymt] = cfgFilterOutputMtype.empty() ? cstr_texthtml :
cfgFilterOutputMtype;
if (!m_forPreview && !m_nomd5) {
string md5, xmd5, reason;
if (MD5File(m_fn, md5, &reason)) {
m_metaData[cstr_dj_keymd5] = MD5HexPrint(md5, xmd5);
} else {
LOGERR("MimeHandlerExec: cant compute md5 for [" << m_fn << "]: " <<
reason << "\n");
}
}
handle_cs(m_metaData[cstr_dj_keymt]);
}