30e3e170150777ca7835b2892751c0d8afd2cb49
[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<pid_t,pid_t> children;
48             boost::mutex m_mutex;
49         public:
50             ~Rep();
51         };
52     }
53 }
54
55 yf::CGI::CGI() : m_p(new Rep)
56 {
57
58 }
59
60 yf::CGI::Rep::~Rep()
61 {
62     std::map<pid_t,pid_t>::const_iterator it;
63     boost::mutex::scoped_lock lock(m_mutex);
64
65     for (it = children.begin(); it != children.end(); it++)
66         kill(it->second, SIGTERM);
67 }
68
69 yf::CGI::~CGI()
70 {
71 }
72
73 void yf::CGI::process(mp::Package &package) const
74 {
75     Z_GDU *zgdu_req = package.request().get();
76     Z_GDU *zgdu_res = 0;
77
78     if (!zgdu_req)
79         return;
80
81     if (zgdu_req->which != Z_GDU_HTTP_Request)
82     {
83         package.move();
84         return;
85     }
86     std::string path_info;
87     std::string query_string;
88     const char *path = zgdu_req->u.HTTP_Request->path;
89     yaz_log(YLOG_LOG, "path=%s", path);
90     const char *p_cp = strchr(path, '?');
91     if (p_cp)
92     {
93         path_info.assign(path, p_cp - path);
94         query_string.assign(p_cp+1);
95     }
96     else
97         path_info.assign(path);
98
99     std::list<CGI::Exec>::const_iterator it;
100     metaproxy_1::odr odr;
101     for (it = m_p->exec_map.begin(); it != m_p->exec_map.end(); it++)
102     {
103         if (it->path.compare(path_info) == 0)
104         {
105             int r;
106             pid_t pid;
107             int status;
108             int fds[2];
109
110             r = pipe(fds);
111             if (r == -1)
112             {
113                 zgdu_res = odr.create_HTTP_Response(
114                     package.session(), zgdu_req->u.HTTP_Request, 400);
115                 package.response() = zgdu_res;
116                 continue;
117             }
118             pid = ::fork();
119             switch (pid)
120             {
121             case 0: /* child */
122                 close(1);
123                 dup(fds[1]);
124                 setenv("PATH_INFO", path_info.c_str(), 1);
125                 setenv("QUERY_STRING", query_string.c_str(), 1);
126                 r = execl(it->program.c_str(), it->program.c_str(), (char *) 0);
127                 if (r == -1)
128                     exit(1);
129                 exit(0);
130                 break;
131             case -1: /* error */
132                 close(fds[0]);
133                 close(fds[1]);
134                 zgdu_res = odr.create_HTTP_Response(
135                     package.session(), zgdu_req->u.HTTP_Request, 400);
136                 package.response() = zgdu_res;
137                 break;
138             default: /* parent */
139                 close(fds[1]);
140                 if (pid)
141                 {
142                     boost::mutex::scoped_lock lock(m_p->m_mutex);
143                     m_p->children[pid] = pid;
144                 }
145                 WRBUF w = wrbuf_alloc();
146                 wrbuf_puts(w, "HTTP/1.1 200 OK\r\n");
147                 while (1)
148                 {
149                     char buf[512];
150                     ssize_t rd = read(fds[0], buf, sizeof buf);
151                     if (rd <= 0)
152                         break;
153                     wrbuf_write(w, buf, rd);
154                 }
155                 close(fds[0]);
156                 waitpid(pid, &status, 0);
157
158                 if (pid)
159                 {
160                     boost::mutex::scoped_lock lock(m_p->m_mutex);
161                     m_p->children.erase(pid);
162                 }
163                 ODR dec = odr_createmem(ODR_DECODE);
164                 odr_setbuf(dec, wrbuf_buf(w), wrbuf_len(w), 0);
165                 r = z_GDU(dec, &zgdu_res, 0, 0);
166                 if (r && zgdu_res)
167                 {
168                     package.response() = zgdu_res;
169                 }
170                 else
171                 {
172                     zgdu_res = odr.create_HTTP_Response(
173                         package.session(), zgdu_req->u.HTTP_Request, 400);
174                     Z_HTTP_Response *hres = zgdu_res->u.HTTP_Response;
175                     z_HTTP_header_add(odr, &hres->headers,
176                                       "Content-Type", "text/plain");
177                     hres->content_buf =
178                         odr_strdup(odr, "Invalid script from script");
179                     hres->content_len = strlen(hres->content_buf);
180                 }
181                 package.response() = zgdu_res;
182                 odr_destroy(dec);
183                 wrbuf_destroy(w);
184                 break;
185             }
186             return;
187         }
188     }
189     package.move();
190 }
191
192 void yf::CGI::configure(const xmlNode *ptr, bool test_only, const char *path)
193 {
194     for (ptr = ptr->children; ptr; ptr = ptr->next)
195     {
196         if (ptr->type != XML_ELEMENT_NODE)
197             continue;
198         if (!strcmp((const char *) ptr->name, "map"))
199         {
200             CGI::Exec exec;
201
202             const struct _xmlAttr *attr;
203             for (attr = ptr->properties; attr; attr = attr->next)
204             {
205                 if (!strcmp((const char *) attr->name,  "path"))
206                     exec.path = mp::xml::get_text(attr->children);
207                 else if (!strcmp((const char *) attr->name, "exec"))
208                     exec.program = mp::xml::get_text(attr->children);
209                 else
210                     throw mp::filter::FilterException
211                         ("Bad attribute "
212                          + std::string((const char *) attr->name)
213                          + " in cgi section");
214             }
215             m_p->exec_map.push_back(exec);
216         }
217         else
218         {
219             throw mp::filter::FilterException("Bad element "
220                                                + std::string((const char *)
221                                                              ptr->name));
222         }
223     }
224 }
225
226 static mp::filter::Base* filter_creator()
227 {
228     return new mp::filter::CGI;
229 }
230
231 extern "C" {
232     struct metaproxy_1_filter_struct metaproxy_1_filter_cgi = {
233         0,
234         "cgi",
235         filter_creator
236     };
237 }
238
239
240 /*
241  * Local variables:
242  * c-basic-offset: 4
243  * c-file-style: "Stroustrup"
244  * indent-tabs-mode: nil
245  * End:
246  * vim: shiftwidth=4 tabstop=8 expandtab
247  */
248