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