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