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