DOCUMENT_ROOT, GATEWAY_INTERFACE MP-564
[metaproxy-moved-to-github.git] / src / filter_cgi.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "filter_cgi.hpp"
20 #include <metaproxy/package.hpp>
21 #include <metaproxy/util.hpp>
22 #include "gduutil.hpp"
23 #include <yaz/zgdu.h>
24 #include <yaz/log.h>
25
26 #include <unistd.h>
27 #include <signal.h>
28 #include <sys/wait.h>
29 #include <sstream>
30
31 #include "config.hpp"
32
33 namespace mp = metaproxy_1;
34 namespace yf = mp::filter;
35
36 namespace metaproxy_1 {
37     namespace filter {
38         class CGI::Exec {
39             friend class Rep;
40             friend class CGI;
41             std::string path;
42             std::string program;
43         };
44         class CGI::Rep {
45             friend class CGI;
46             std::list<CGI::Exec> exec_map;
47             std::map<std::string,std::string> env_map;
48             std::map<pid_t,pid_t> children;
49             boost::mutex m_mutex;
50             std::string documentroot;
51             void child(Z_HTTP_Request *, const CGI::Exec *);
52         public:
53             ~Rep();
54         };
55     }
56 }
57
58 yf::CGI::CGI() : m_p(new Rep)
59 {
60
61 }
62
63 yf::CGI::Rep::~Rep()
64 {
65     std::map<pid_t,pid_t>::const_iterator it;
66     boost::mutex::scoped_lock lock(m_mutex);
67
68     for (it = children.begin(); it != children.end(); it++)
69         kill(it->second, SIGTERM);
70 }
71
72 yf::CGI::~CGI()
73 {
74 }
75
76 void yf::CGI::Rep::child(Z_HTTP_Request *hreq, const CGI::Exec *it)
77 {
78     const char *path_cstr = hreq->path;
79     std::string path(path_cstr);
80     const char *program_cstr = it->program.c_str();
81     std::string script_name(path, 0, it->path.length());
82     std::string rest(path, it->path.length());
83     std::string query_string;
84     std::string path_info;
85     size_t qpos = rest.find('?');
86     if (qpos == std::string::npos)
87         path_info = rest;
88     else
89     {
90         query_string.assign(rest, qpos + 1, std::string::npos);
91         path_info.assign(rest, 0, qpos);
92     }
93     setenv("REQUEST_METHOD", hreq->method, 1);
94     setenv("REQUEST_URI", path_cstr, 1);
95     setenv("SCRIPT_NAME", script_name.c_str(), 1);
96     setenv("PATH_INFO", path_info.c_str(), 1);
97     setenv("QUERY_STRING", query_string.c_str(), 1);
98     const char *v;
99     v = z_HTTP_header_lookup(hreq->headers, "Cookie");
100     if (v)
101         setenv("HTTP_COOKIE", v, 1);
102     v = z_HTTP_header_lookup(hreq->headers, "User-Agent");
103     if (v)
104         setenv("HTTP_USER_AGENT", v, 1);
105     v = z_HTTP_header_lookup(hreq->headers, "Accept");
106     if (v)
107         setenv("HTTP_ACCEPT", v, 1);
108     v = z_HTTP_header_lookup(hreq->headers, "Accept-Encoding");
109     if (v)
110         setenv("HTTP_ACCEPT_ENCODING", v, 1);
111     setenv("DOCUMENT_ROOT", documentroot.c_str(), 1);
112     setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
113     // apply user-defined environment
114     std::map<std::string,std::string>::const_iterator it_e;
115     for (it_e = env_map.begin();
116          it_e != env_map.end(); it_e++)
117         setenv(it_e->first.c_str(), it_e->second.c_str(), 1);
118     // change directory to configuration root
119     // then to CGI program directory (could be relative)
120     chdir(documentroot.c_str());
121     char *program = xstrdup(program_cstr);
122     char *cp = strrchr(program, '/');
123     if (cp)
124     {
125         *cp++ = '\0';
126         chdir(program);
127     }
128     else
129         cp = program;
130     int r = execl(cp, cp, (char *) 0);
131     if (r == -1)
132         exit(1);
133     exit(0);
134 }
135
136 void yf::CGI::process(mp::Package &package) const
137 {
138     Z_GDU *zgdu_req = package.request().get();
139     Z_GDU *zgdu_res = 0;
140
141     if (!zgdu_req)
142         return;
143
144     if (zgdu_req->which != Z_GDU_HTTP_Request)
145     {
146         package.move();
147         return;
148     }
149
150
151     std::list<CGI::Exec>::const_iterator it;
152     metaproxy_1::odr odr;
153     Z_HTTP_Request *hreq = zgdu_req->u.HTTP_Request;
154     const char *path_cstr = hreq->path;
155     for (it = m_p->exec_map.begin(); it != m_p->exec_map.end(); it++)
156     {
157         if (strncmp(it->path.c_str(), path_cstr, it->path.length()) == 0)
158         {
159             int fds[2];
160             int r = pipe(fds);
161             if (r == -1)
162             {
163                 zgdu_res = odr.create_HTTP_Response(
164                     package.session(), hreq, 400);
165                 package.response() = zgdu_res;
166                 continue;
167             }
168             int status;
169             pid_t pid = ::fork();
170             switch (pid)
171             {
172             case 0: /* child */
173                 close(1);
174                 dup(fds[1]);
175                 m_p->child(hreq, &(*it));
176                 break;
177             case -1: /* error */
178                 close(fds[0]);
179                 close(fds[1]);
180                 zgdu_res = odr.create_HTTP_Response(
181                     package.session(), hreq, 400);
182                 package.response() = zgdu_res;
183                 break;
184             default: /* parent */
185                 close(fds[1]);
186                 if (pid)
187                 {
188                     boost::mutex::scoped_lock lock(m_p->m_mutex);
189                     m_p->children[pid] = pid;
190                 }
191                 WRBUF w = wrbuf_alloc();
192                 wrbuf_puts(w, "HTTP/1.1 200 OK\r\n");
193                 while (1)
194                 {
195                     char buf[512];
196                     ssize_t rd = read(fds[0], buf, sizeof buf);
197                     if (rd <= 0)
198                         break;
199                     wrbuf_write(w, buf, rd);
200                 }
201                 close(fds[0]);
202                 waitpid(pid, &status, 0);
203
204                 if (pid)
205                 {
206                     boost::mutex::scoped_lock lock(m_p->m_mutex);
207                     m_p->children.erase(pid);
208                 }
209                 ODR dec = odr_createmem(ODR_DECODE);
210                 odr_setbuf(dec, wrbuf_buf(w), wrbuf_len(w), 0);
211                 r = z_GDU(dec, &zgdu_res, 0, 0);
212                 if (r && zgdu_res)
213                 {
214                     package.response() = zgdu_res;
215                 }
216                 else
217                 {
218                     zgdu_res = odr.create_HTTP_Response(
219                         package.session(), zgdu_req->u.HTTP_Request, 400);
220                     Z_HTTP_Response *hres = zgdu_res->u.HTTP_Response;
221                     z_HTTP_header_add(odr, &hres->headers,
222                                       "Content-Type", "text/plain");
223                     hres->content_buf =
224                         odr_strdup(odr, "Invalid script from script");
225                     hres->content_len = strlen(hres->content_buf);
226                 }
227                 package.response() = zgdu_res;
228                 odr_destroy(dec);
229                 wrbuf_destroy(w);
230                 break;
231             }
232             return;
233         }
234     }
235     package.move();
236 }
237
238 void yf::CGI::configure(const xmlNode *ptr, bool test_only, const char *path)
239 {
240     yaz_log(YLOG_LOG, "cgi::configure path=%s", path);
241     for (ptr = ptr->children; ptr; ptr = ptr->next)
242     {
243         if (ptr->type != XML_ELEMENT_NODE)
244             continue;
245         if (!strcmp((const char *) ptr->name, "map"))
246         {
247             CGI::Exec exec;
248
249             const struct _xmlAttr *attr;
250             for (attr = ptr->properties; attr; attr = attr->next)
251             {
252                 if (!strcmp((const char *) attr->name,  "path"))
253                     exec.path = mp::xml::get_text(attr->children);
254                 else if (!strcmp((const char *) attr->name, "exec"))
255                     exec.program = mp::xml::get_text(attr->children);
256                 else
257                     throw mp::filter::FilterException
258                         ("Bad attribute "
259                          + std::string((const char *) attr->name)
260                          + " in cgi section");
261             }
262             m_p->exec_map.push_back(exec);
263         }
264         else if (!strcmp((const char *) ptr->name, "env"))
265         {
266             std::string name, value;
267
268             const struct _xmlAttr *attr;
269             for (attr = ptr->properties; attr; attr = attr->next)
270             {
271                 if (!strcmp((const char *) attr->name,  "name"))
272                     name = mp::xml::get_text(attr->children);
273                 else if (!strcmp((const char *) attr->name, "value"))
274                     value = mp::xml::get_text(attr->children);
275                 else
276                     throw mp::filter::FilterException
277                         ("Bad attribute "
278                          + std::string((const char *) attr->name)
279                          + " in cgi section");
280             }
281             if (name.length() > 0)
282                 m_p->env_map[name] = value;
283         }
284         else if (!strcmp((const char *) ptr->name, "documentroot"))
285         {
286             m_p->documentroot = path;
287         }
288         else
289         {
290             throw mp::filter::FilterException("Bad element "
291                                                + std::string((const char *)
292                                                              ptr->name));
293         }
294     }
295     if (m_p->documentroot.length() == 0)
296         m_p->documentroot = ".";
297 }
298
299 static mp::filter::Base* filter_creator()
300 {
301     return new mp::filter::CGI;
302 }
303
304 extern "C" {
305     struct metaproxy_1_filter_struct metaproxy_1_filter_cgi = {
306         0,
307         "cgi",
308         filter_creator
309     };
310 }
311
312
313 /*
314  * Local variables:
315  * c-basic-offset: 4
316  * c-file-style: "Stroustrup"
317  * indent-tabs-mode: nil
318  * End:
319  * vim: shiftwidth=4 tabstop=8 expandtab
320  */
321