The configure method takes test_only flag so we can avoid
[metaproxy-moved-to-github.git] / src / filter_http_file.cpp
1 /* $Id: filter_http_file.cpp,v 1.10 2008-02-20 15:07:51 adam Exp $
2    Copyright (c) 2005-2007, Index Data.
3
4 This file is part of Metaproxy.
5
6 Metaproxy is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Metaproxy; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 #include "config.hpp"
23 #include "filter.hpp"
24 #include "package.hpp"
25 #include "util.hpp"
26 #include "filter_http_file.hpp"
27
28 #include <yaz/zgdu.h>
29
30 #include <boost/thread/mutex.hpp>
31
32 #include <list>
33 #include <map>
34 #include <iostream>
35
36 #if HAVE_SYS_TYPES_H
37 #include <sys/types.h>
38 #endif
39
40 #if HAVE_SYS_STAT_H
41 #include <sys/stat.h>
42 #endif
43
44 namespace mp = metaproxy_1;
45 namespace yf = mp::filter;
46
47 namespace metaproxy_1 {
48     namespace filter {
49         struct HttpFile::Area {
50             std::string m_url_path_prefix;
51             std::string m_file_root;
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             std::string get_mime_type(std::string &fname);
74         };
75     }
76 }
77
78 yf::HttpFile::Mime::Mime() {}
79
80 yf::HttpFile::Mime::Mime(std::string type) : m_type(type) {}
81
82 yf::HttpFile::HttpFile() : m_p(new Rep)
83 {
84 #if 0
85     m_p->m_ext_to_map["html"] = Mime("text/html");
86     m_p->m_ext_to_map["htm"] = Mime("text/html");
87     m_p->m_ext_to_map["png"] = Mime("image/png");
88     m_p->m_ext_to_map["txt"] = Mime("text/plain");
89     m_p->m_ext_to_map["text"] = Mime("text/plain");
90     m_p->m_ext_to_map["asc"] = Mime("text/plain");
91     m_p->m_ext_to_map["xml"] = Mime("application/xml");
92     m_p->m_ext_to_map["xsl"] = Mime("application/xml");
93 #endif
94 #if 0
95     Area a;
96     a.m_url_path_prefix = "/etc";
97     a.m_file_root = ".";
98     m_p->m_area_list.push_back(a);
99 #endif
100 }
101
102 yf::HttpFile::~HttpFile()
103 {
104 }
105
106 std::string yf::HttpFile::Rep::get_mime_type(std::string &fname)
107 {
108     std::string file_part = fname;
109     std::string::size_type p = fname.find_last_of('/');
110     
111     if (p != std::string::npos)
112         file_part = fname.substr(p+1);
113
114     p = file_part.find_last_of('.');
115     std::string content_type;
116     if (p != std::string::npos)
117     {
118         std::string ext = file_part.substr(p+1);
119         MimeMap::const_iterator it = m_ext_to_map.find(ext);
120
121         if (it != m_ext_to_map.end())
122             content_type = it->second.m_type;
123     }
124     if (content_type.length() == 0)
125         content_type = "application/octet-stream";
126     return content_type;
127 }
128
129 void yf::HttpFile::Rep::fetch_file(mp::Session &session,
130                                    Z_HTTP_Request *req,
131                                    std::string &fname, mp::Package &package)
132 {
133     mp::odr o;
134     
135     FILE *f = fopen(fname.c_str(), "rb");
136     if (!f)
137     {
138         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
139         package.response() = gdu;
140         return;
141     }
142     if (fseek(f, 0L, SEEK_END) == -1)
143     {
144         fclose(f);
145         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
146         package.response() = gdu;
147         return;
148     }
149     long sz = ftell(f);
150     if (sz > 1000000L)
151     {
152         fclose(f);
153         Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
154         package.response() = gdu;
155         return;
156     }
157     rewind(f);
158
159     Z_GDU *gdu = o.create_HTTP_Response(session, req, 200);
160
161     Z_HTTP_Response *hres = gdu->u.HTTP_Response;
162     hres->content_len = sz;
163     hres->content_buf = (char*) odr_malloc(o, hres->content_len);
164     fread(hres->content_buf, 1, hres->content_len, f);
165
166     fclose(f);
167     
168     std::string content_type = get_mime_type(fname);
169
170     z_HTTP_header_add(o, &hres->headers,
171                       "Content-Type", content_type.c_str());
172     package.response() = gdu;
173 }
174
175 void yf::HttpFile::Rep::fetch_uri(mp::Session &session,
176                                   Z_HTTP_Request *req, mp::Package &package)
177 {
178     bool sane = true;
179     std::string path = req->path;
180
181     // we don't consider ?, # yet..
182
183     // we don't allow ..
184     std::string::size_type p = path.find("..");
185     if (p != std::string::npos)
186         sane = false;
187
188     if (sane)
189     {
190         AreaList::const_iterator it;
191         for (it = m_area_list.begin(); it != m_area_list.end(); it++)
192         {
193             std::string::size_type l = it->m_url_path_prefix.length();
194
195             if (path.compare(0, l, it->m_url_path_prefix) == 0)
196             {
197                 std::string fname = it->m_file_root + path.substr(l);
198                 std::cout << "fname = " << fname << "\n";
199                 fetch_file(session, req, fname, package);
200                 return;
201             }
202         }
203     }
204     mp::odr o;
205     Z_GDU *gdu = o.create_HTTP_Response(session, req, 404);
206     package.response() = gdu;
207 }
208                          
209 void yf::HttpFile::process(mp::Package &package) const
210 {
211     Z_GDU *gdu = package.request().get();
212     if (gdu && gdu->which == Z_GDU_HTTP_Request)
213         m_p->fetch_uri(package.session(), gdu->u.HTTP_Request, package);
214     else
215         package.move();
216 }
217
218 void mp::filter::HttpFile::configure(const xmlNode * ptr, bool test_only)
219 {
220     for (ptr = ptr->children; ptr; ptr = ptr->next)
221     {
222         if (ptr->type != XML_ELEMENT_NODE)
223             continue;
224         if (!strcmp((const char *) ptr->name, "mimetypes"))
225         {
226             std::string fname = mp::xml::get_text(ptr);
227
228             mp::PlainFile f;
229
230             if (!f.open(fname))
231             {
232                 throw mp::filter::FilterException
233                     ("Can not open mime types file " + fname);
234             }
235             
236             std::vector<std::string> args;
237             while (f.getline(args))
238             {
239                 size_t i;
240                 for (i = 1; i<args.size(); i++)
241                     m_p->m_ext_to_map[args[i]] = args[0];
242             }
243         }
244         else if (!strcmp((const char *) ptr->name, "area"))
245         {
246             xmlNode *a_node = ptr->children;
247             Area a;
248             for (; a_node; a_node = a_node->next)
249             {
250                 if (a_node->type != XML_ELEMENT_NODE)
251                     continue;
252                 
253                 if (mp::xml::is_element_mp(a_node, "documentroot"))
254                     a.m_file_root = mp::xml::get_text(a_node);
255                 else if (mp::xml::is_element_mp(a_node, "prefix"))
256                     a.m_url_path_prefix = mp::xml::get_text(a_node);
257                 else
258                     throw mp::filter::FilterException
259                         ("Bad element " 
260                          + std::string((const char *) a_node->name)
261                          + " in area section"
262                             );
263             }
264             if (a.m_file_root.length())
265             {
266                 m_p->m_area_list.push_back(a);
267             }
268         }
269         else
270         {
271             throw mp::filter::FilterException
272                 ("Bad element " 
273                  + std::string((const char *) ptr->name)
274                  + " in virt_db filter");
275         }
276     }
277 }
278
279 static mp::filter::Base* filter_creator()
280 {
281     return new mp::filter::HttpFile;
282 }
283
284 extern "C" {
285     struct metaproxy_1_filter_struct metaproxy_1_filter_http_file = {
286         0,
287         "http_file",
288         filter_creator
289     };
290 }
291
292
293 /*
294  * Local variables:
295  * c-basic-offset: 4
296  * indent-tabs-mode: nil
297  * c-file-style: "stroustrup"
298  * End:
299  * vim: shiftwidth=4 tabstop=8 expandtab
300  */