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