18bdd571f83e27a1b0ccf0702807dff09b8de866
[metaproxy-moved-to-github.git] / src / filter_http_file.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 "config.hpp"
20 #include <metaproxy/filter.hpp>
21 #include <metaproxy/package.hpp>
22 #include <metaproxy/util.hpp>
23 #include "filter_http_file.hpp"
24
25 #include <yaz/zgdu.h>
26 #include <yaz/log.h>
27
28 #include <boost/thread/mutex.hpp>
29
30 #include <list>
31 #include <map>
32 #include <iostream>
33
34 #if HAVE_SYS_TYPES_H
35 #include <sys/types.h>
36 #endif
37
38 #if HAVE_SYS_STAT_H
39 #include <sys/stat.h>
40 #endif
41
42 namespace mp = metaproxy_1;
43 namespace yf = mp::filter;
44
45 namespace metaproxy_1 {
46     namespace filter {
47         struct HttpFile::Area {
48             std::string m_url_path_prefix;
49             std::string m_file_root;
50             bool m_raw;
51             Area();
52         };
53         class HttpFile::Mime {
54             friend class Rep;
55             std::string m_type;
56         public:
57             Mime(std::string type);
58             Mime();
59         };
60         class HttpFile::Rep {
61             friend class HttpFile;
62
63             typedef std::list<Area> AreaList;
64             typedef std::map<std::string,Mime> MimeMap;
65
66             MimeMap m_ext_to_map;
67             AreaList m_area_list;
68             void fetch_uri(mp::Session &session,
69                            Z_HTTP_Request *req, mp::Package &package);
70             void fetch_file(mp::Session &session,
71                             Z_HTTP_Request *req,
72                             std::string &fname, mp::Package &package,
73                             bool raw);
74             std::string get_mime_type(std::string &fname);
75         };
76     }
77 }
78
79 yf::HttpFile::Area::Area() : m_raw(false)
80 {
81 }
82
83 yf::HttpFile::Mime::Mime() {}
84
85 yf::HttpFile::Mime::Mime(std::string type) : m_type(type) {}
86
87 yf::HttpFile::HttpFile() : m_p(new Rep)
88 {
89 #if 0
90     m_p->m_ext_to_map["html"] = Mime("text/html");
91     m_p->m_ext_to_map["htm"] = Mime("text/html");
92     m_p->m_ext_to_map["png"] = Mime("image/png");
93     m_p->m_ext_to_map["txt"] = Mime("text/plain");
94     m_p->m_ext_to_map["text"] = Mime("text/plain");
95     m_p->m_ext_to_map["asc"] = Mime("text/plain");
96     m_p->m_ext_to_map["xml"] = Mime("application/xml");
97     m_p->m_ext_to_map["xsl"] = Mime("application/xml");
98 #endif
99 #if 0
100     Area a;
101     a.m_url_path_prefix = "/etc";
102     a.m_file_root = ".";
103     m_p->m_area_list.push_back(a);
104 #endif
105 }
106
107 yf::HttpFile::~HttpFile()
108 {
109 }
110
111 std::string yf::HttpFile::Rep::get_mime_type(std::string &fname)
112 {
113     std::string file_part = fname;
114     std::string::size_type p = fname.find_last_of('/');
115
116     if (p != std::string::npos)
117         file_part = fname.substr(p+1);
118
119     p = file_part.find_last_of('.');
120     std::string content_type;
121     if (p != std::string::npos)
122     {
123         std::string ext = file_part.substr(p+1);
124         MimeMap::const_iterator it = m_ext_to_map.find(ext);
125
126         if (it != m_ext_to_map.end())
127             content_type = it->second.m_type;
128     }
129     if (content_type.length() == 0)
130         content_type = "application/octet-stream";
131     return content_type;
132 }
133
134 void yf::HttpFile::Rep::fetch_file(mp::Session &session,
135                                    Z_HTTP_Request *req,
136                                    std::string &fname, mp::Package &package,
137                                    bool raw)
138 {
139     mp::odr o(ODR_ENCODE);
140
141     if (strcmp(req->method, "GET"))
142     {
143         Z_GDU *gdu = o.create_HTTP_Response(session, req, 405);
144         package.response() = gdu;
145         return;
146     }
147
148     FILE *f = fopen(fname.c_str(), "rb");
149     if (!f)
150     {
151         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
152         package.response() = gdu;
153         return;
154     }
155     if (fseek(f, 0L, SEEK_END) == -1)
156     {
157         fclose(f);
158         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
159         package.response() = gdu;
160         return;
161     }
162     long sz = ftell(f);
163     if (sz > 1000000L)
164     {
165         fclose(f);
166         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
167         package.response() = gdu;
168         return;
169     }
170     rewind(f);
171     char *fbuf = (char*) odr_malloc(o, sz);
172     if (sz > 0)
173     {
174         if (fread(fbuf, sz, 1, f) != 1)
175         {
176             Z_GDU *gdu = o.create_HTTP_Response(session, req, 500);
177             package.response() = gdu;
178             fclose(f);
179             return;
180         }
181     }
182     fclose(f);
183
184     Z_GDU *gdu = 0;
185     mp::odr decode(ODR_DECODE);
186     if (raw)
187     {
188         odr_setbuf(decode, (char *) fbuf, sz, 0);
189         int r = z_GDU(decode, &gdu, 0, 0);
190         if (!r)
191         {
192             gdu = o.create_HTTP_Response(session, req, 500);
193         }
194     }
195     else
196     {
197         gdu = o.create_HTTP_Response(session, req, 200);
198         Z_HTTP_Response *hres = gdu->u.HTTP_Response;
199         hres->content_len = sz;
200         hres->content_buf = fbuf;
201         std::string content_type = get_mime_type(fname);
202         z_HTTP_header_add(o, &hres->headers,
203                       "Content-Type", content_type.c_str());
204     }
205     package.response() = gdu;
206 }
207
208 void yf::HttpFile::Rep::fetch_uri(mp::Session &session,
209                                   Z_HTTP_Request *req, mp::Package &package)
210 {
211     bool sane = true;
212     std::string::size_type p;
213     std::string path = req->path;
214
215     p = path.find("#");
216     if (p != std::string::npos)
217         path = path.erase(p);
218
219     p = path.find("?");
220     if (p != std::string::npos)
221         path = path.erase(p);
222
223     path = mp::util::uri_decode(path);
224
225     // we don't allow ..
226     p = path.find("..");
227     if (p != std::string::npos)
228         sane = false;
229
230     if (sane)
231     {
232         AreaList::const_iterator it;
233         for (it = m_area_list.begin(); it != m_area_list.end(); it++)
234         {
235             std::string::size_type l = it->m_url_path_prefix.length();
236
237             if (path.compare(0, l, it->m_url_path_prefix) == 0)
238             {
239                 std::string fname = it->m_file_root + path.substr(l);
240                 package.log("http_file", YLOG_LOG, "%s", fname.c_str());
241                 fetch_file(session, req, fname, package, it->m_raw);
242                 return;
243             }
244         }
245     }
246     package.move();
247 }
248
249 void yf::HttpFile::process(mp::Package &package) const
250 {
251     Z_GDU *gdu = package.request().get();
252     if (gdu && gdu->which == Z_GDU_HTTP_Request)
253         m_p->fetch_uri(package.session(), gdu->u.HTTP_Request, package);
254     else
255         package.move();
256 }
257
258 void mp::filter::HttpFile::configure(const xmlNode * ptr, bool test_only,
259                                      const char *path)
260 {
261     for (ptr = ptr->children; ptr; ptr = ptr->next)
262     {
263         if (ptr->type != XML_ELEMENT_NODE)
264             continue;
265         if (!strcmp((const char *) ptr->name, "mimetypes"))
266         {
267             std::string fname = mp::xml::get_text(ptr);
268
269             mp::PlainFile f;
270
271             if (!f.open(fname))
272             {
273                 throw mp::filter::FilterException
274                     ("Can not open mime types file " + fname);
275             }
276
277             std::vector<std::string> args;
278             while (f.getline(args))
279             {
280                 size_t i;
281                 for (i = 1; i<args.size(); i++)
282                     m_p->m_ext_to_map[args[i]] = args[0];
283             }
284         }
285         else if (!strcmp((const char *) ptr->name, "area"))
286         {
287             xmlNode *a_node = ptr->children;
288             Area a;
289
290             for (; a_node; a_node = a_node->next)
291             {
292                 if (a_node->type != XML_ELEMENT_NODE)
293                     continue;
294
295                 if (mp::xml::is_element_mp(a_node, "documentroot"))
296                     a.m_file_root = mp::xml::get_text(a_node);
297                 else if (mp::xml::is_element_mp(a_node, "prefix"))
298                     a.m_url_path_prefix = mp::xml::get_text(a_node);
299                 else if (mp::xml::is_element_mp(a_node, "raw"))
300                     a.m_raw = mp::xml::get_bool(a_node, false);
301                 else
302                     throw mp::filter::FilterException
303                         ("Bad element "
304                          + std::string((const char *) a_node->name)
305                          + " in area section"
306                             );
307             }
308             if (a.m_file_root.length())
309             {
310                 m_p->m_area_list.push_back(a);
311             }
312         }
313         else
314         {
315             throw mp::filter::FilterException
316                 ("Bad element "
317                  + std::string((const char *) ptr->name)
318                  + " in virt_db filter");
319         }
320     }
321 }
322
323 static mp::filter::Base* filter_creator()
324 {
325     return new mp::filter::HttpFile;
326 }
327
328 extern "C" {
329     struct metaproxy_1_filter_struct metaproxy_1_filter_http_file = {
330         0,
331         "http_file",
332         filter_creator
333     };
334 }
335
336
337 /*
338  * Local variables:
339  * c-basic-offset: 4
340  * c-file-style: "Stroustrup"
341  * indent-tabs-mode: nil
342  * End:
343  * vim: shiftwidth=4 tabstop=8 expandtab
344  */
345