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