root/http/compose.cc

Revision 99057e4d73dbb905f06f5ccc4ea49708b8aaa0a8, 19.1 KB (checked in by Antti-Juhani Kaijanaho <antti-juhani@…>, 20 months ago)

Rework entity handling

Signed-off-by: Antti-Juhani Kaijanaho <antti-juhani@…>

  • Property mode set to 100644
Line 
1/*  This file is part of Alue, the multiprotocol Internet discussion daemon
2
3    Copyright © 2009, 2010 Antti-Juhani Kaijanaho
4
5    Alue is free software: you can redistribute it and/or modify it
6    under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    Alue is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with Alue.  If not, see <http://www.gnu.org/licenses/>.
17
18 */
19
20#include "authn.hh"
21#include "compose.hh"
22#include "error_resource.hh"
23#include "reauthn_resource.hh"
24#include "redir_resource.hh"
25#include "request.hh"
26#include "templated_resource.hh"
27#include "session.hh"
28
29#include "../db/db.hh"
30#include "../db/msg.hh"
31#include "../db/threaded.hh"
32#include "../html/util.hh"
33#include "../logger/logline.hh"
34#include "../msg/entity.hh"
35#include "../msg/lexutils.hh"
36#include "../msg/text_plain.hh"
37#include "../msg/util.hh"
38#include "../server.hh"
39#include "../tlate/empty_value.hh"
40#include "../tlate/group_value.hh"
41#include "../tlate/list_value.hh"
42#include "../tlate/missing_value.hh"
43#include "../tlate/tlate.hh"
44
45#include <boost/shared_ptr.hpp>
46
47namespace
48{
49        class listed_group_value : public tlate::group_value
50        {
51                bool post_to;
52                bool followup_to;
53        public:
54                listed_group_value(boost::shared_ptr<db::group> gr,
55                                   boost::shared_ptr<db::user> u,
56                                   bool post_to, bool followup_to)
57                        : group_value(gr, u)
58                        , post_to(post_to)
59                        , followup_to(followup_to)
60                        {}
61
62                value::const_ptr get(std::string) const;
63        };
64
65        tlate::value::const_ptr listed_group_value::get(std::string var) const
66        {
67                /**/ if (var == "post-to")
68                {
69                        tlate::value::ptr rv;
70                        if (post_to) rv.reset(new tlate::empty_value);
71                        return rv;
72                }
73                else if (var == "followup-to")
74                {
75                        tlate::value::ptr rv;
76                        if (followup_to) rv.reset(new tlate::empty_value);
77                        return rv;
78                }
79                else
80                        return group_value::get(var);
81        }
82}
83
84namespace http
85{
86        std::string compose::get_posting_uri(db::group::const_ptr gr)
87        {
88                return std::string("/compose?group=") +
89                        uri::percent_encode(gr->name());
90        }
91
92        std::string compose::get_followup_uri(db::msg::const_ptr m)
93        {
94                return std::string("/compose?precursor=") +
95                        uri::percent_encode(m->msgid());
96        }
97
98        void compose::reload_action(boost::shared_ptr<request> req,
99                                    tlate::data_model::ptr am,
100                                    std::string error_message)
101        {
102                typedef request::form_data_iterator cit_type;
103                std::pair<cit_type, cit_type> its;
104
105                if (!error_message.empty())
106                        am->insert("error", html::quote(error_message, false));
107                am->insert("subject", req->get_form_field("subject"));
108                am->insert("references", req->get_form_field("references"));
109                am->insert("body", req->get_form_field("body"));
110                   
111                std::list<std::string> ngs;
112                its = req->get_form_fields("newsgroups");
113                for (cit_type it = its.first; it != its.second; it++)
114                        ngs.push_back(it->second);
115
116                std::list<std::string> fus;
117                its = req->get_form_fields("followups");
118                for (cit_type it = its.first; it != its.second; it++)
119                        fus.push_back(it->second);
120
121                return respond_compose(req, am, ngs, fus);
122        }
123
124        void compose::post_action(boost::shared_ptr<request> req,
125                                  tlate::data_model::ptr am)
126        {
127                std::ostringstream os;
128
129                typedef std::multimap<std::string,std::string>::const_iterator
130                        cit_type;
131                std::pair<cit_type, cit_type> its;
132
133                boost::shared_ptr<db::user> usr= req->get_session()->get_user();
134                os << "From: "
135                   << msg::make_phrase(usr->get_display_name())
136                   << " <"
137                   << usr->get_display_email()
138                   << ">\r\n";
139
140                os << "Newsgroups: ";
141                bool first = true;
142                its = req->get_form_fields("newsgroups");
143                for (cit_type it = its.first; it != its.second; it++)
144                {
145                        if (!first) os << ",";
146                        first = false;
147                        os << it->second;
148                }
149                os << "\r\n";
150
151                its = req->get_form_fields("followups");
152                if (its.first != its.second)
153                {
154                        os << "Followup-To: ";
155                        first = true;
156                        for (cit_type it = its.first; it != its.second; it++)
157                        {
158                                if (!first) os << ",";
159                                first = false;
160                                os << it->second;
161                        }
162                        os << "\r\n";
163                }
164
165                os << "Subject: "
166                   << msg::encode_unstructured(req->get_form_field("subject"))
167                   << "\r\n";
168
169                std::string refs = req->get_form_field("references");
170                if (!refs.empty())
171                {
172                        os << "References: " << refs << "\r\n";
173
174                }
175
176
177                std::string content_type =
178                        "text/plain; charset=\"utf-8\";"
179                        " format=flowed; delsp=yes";
180
181                msg::text_plain tp(msg::crlf_canonize(req->get_form_field("body")),
182                                   msg::content_type(content_type));
183
184                std::ostringstream body;
185                for (size_t i = 0; i < tp.num_paras(); i++)
186                {
187                        const msg::text_plain::para &p = tp.get_para(i);
188                        const std::string qs(p.quote_depth(), '>');
189                        for (size_t j = 0; j < p.num_lines(); j++)
190                        {
191                                std::string line = p.get_line(j);
192                                if (p.quote_depth() == 0 &&
193                                    !line.empty() && line[0] == 'F')
194                                {
195                                        line.insert(0, " ");
196                                }
197                                size_t ll = 78 - p.quote_depth();
198                                while (!line.empty())
199                                {
200                                        size_t sp = ll;
201                                        while (sp > 0 && line[sp] != ' ') sp--;
202                                        if (sp == 0)
203                                        {
204                                                body << qs
205                                                     << line.substr(0, ll)
206                                                     << " \r\n";
207                                                line.erase(0, ll);
208                                        }
209                                        else
210                                        {
211                                                body << qs
212                                                     << line.substr(0, sp+1)
213                                                     << " \r\n";
214                                                line.erase(0, sp+1);
215                                        }
216                                }
217                                if (j < p.num_lines()-1) body << qs << " \r\n";
218                        }
219                        body << qs << "\r\n";
220                }
221                os << "User-Agent: alue/0 (HTTPS)\r\n"
222                   << "Content-Type: " << content_type << "\r\n"
223                   << "\r\n"
224                   << body.str()
225                   << "\r\n";
226
227                db::msg::ptr m;
228                try
229                {
230                        msg::entity::ptr e = msg::entity::mk(os.str(), false);
231                        msg::complete_mime(e);
232                        m = cb.dbase().file(e, req->get_peer(), usr, cb);
233                }
234                catch (std::exception &e)
235                {
236                        return reload_action(req, am, e.what());
237                }
238                logger::logline ll;
239                ll << "httpd for " 
240                   << req->get_peer()
241                   << ": POST created " 
242                   << m->msgid();
243                ll.close();
244                boost::shared_ptr<resource> rr
245                        (new redir_resource(cb,
246                                            "/id/" +
247                                            uri::percent_encode(m->msgid()),
248                                            "303 See other"));
249                throw resource_exception(rr);
250        }
251
252        void compose::set_attributes(boost::shared_ptr<request> req,
253                                     tlate::data_model::ptr am)
254        {
255                boost::shared_ptr<resource> resp;
256                if (req->get_path() != "/compose")
257                {
258                        resp.reset(new error_resource(cb, "404 Not found"));
259                        throw resource_exception(resp);
260                }
261                if (!req->is_authenticated())
262                {
263                        resp.reset(new reauthn_resource(cb));
264                        throw resource_exception(resp);
265                }
266
267                if (req->has_form_field("post_button"))
268                {
269                        if (req->get_method() == "POST")
270                                return post_action(req, am);
271                        else
272                                return reload_action(req, am);
273                }
274
275                if (req->has_form_field("group"))
276                {
277                        boost::shared_ptr<db::group> group;
278                        try
279                        {
280                                group = cb.dbase().lookup_group
281                                        (req->get_form_field("group"));
282                        }
283                        catch (db::no_such_group)
284                        {
285                                resp.reset(new error_resource
286                                           (cb, "404 No such group " +
287                                            req->get_form_field("group")));
288                                throw resource_exception(resp);
289                        }
290                        return new_thread(req, am, group);
291                }
292
293                if (req->has_form_field("precursor"))
294                {
295                        boost::shared_ptr<db::msg> art;
296                        std::string pre = req->get_form_field("precursor");
297                        try
298                        {
299                                art = cb.dbase().lookup_msgid(pre);
300                        }
301                        catch (db::no_such_article)
302                        {
303                                resp.reset(new error_resource
304                                           (cb, "404 No such article " + pre));
305                                throw resource_exception(resp);
306                        }
307                        return followup(req, am, art);
308                }
309                // FIXME
310                resp.reset(new error_resource(cb, "404 Not found"));
311                throw resource_exception(resp);
312        }
313
314        void compose::followup(boost::shared_ptr<request> req,
315                               tlate::data_model::ptr am,
316                               boost::shared_ptr<db::msg> precursor)
317        {
318                std::string (*const q)(std::string,bool) = html::quote;
319                //std::string (*const p)(std::string) = uri::percent_encode;
320
321                std::string subject =
322                        precursor->get_entity()->get_field("Subject", false);
323                util::strip_crlf(subject);
324                if (subject.substr(0, 4) != "Re: ")
325                        subject.insert(0, "Re: ");
326                am->insert("subject", subject);
327
328                std::string fups = precursor->get_entity()->get_field
329                        ("Followup-To", false);
330                util::strip(fups);
331                if (fups.empty() || fups == "poster") // FIXME
332                {
333                        fups = precursor->get_entity()->get_field("Newsgroups", false);
334                        util::strip(fups);
335                }
336                std::list<std::string> ngs = msg::parse_newsgroups_list(fups);
337
338
339#if 0
340                std::string distrib = precursor->get_entity()->get_field
341                        ("Distribution", false);
342                util::strip_crlf(distrib);
343                if (!distrib.empty())
344                        ss << "Distribution: " << distrib << '\n';
345#endif
346
347                std::string refs = precursor->get_entity()->get_field
348                        ("References", false);
349                util::strip_crlf(refs);
350                if (!refs.empty()) refs += ' ';
351                refs += precursor->msgid();
352#if 0
353                bool folded = false;
354                for (size_t i = 0; i < refs.length(); i++)
355                {
356                        switch (refs[i])
357                        {
358                        case ' ': case '\t':
359                                if (!folded)
360                                {
361                                        refs.insert(i, "\r\n");
362                                        i += 2;
363                                        folded = true;
364                                }
365                                break;
366                        case '\r': case '\n':
367                                refs[i] = ' ';
368                                folded = true;
369                                break;
370                        default:
371                                folded = false;
372                        }
373                }
374#endif
375                am->insert("references", q(refs, false));
376
377                std::ostringstream body;
378
379                std::string from =
380                        precursor->get_entity()->get_field("From", false);
381                util::strip_crlf(from);
382                body << from << " wrote: \n\n";
383
384                msg::text_plain tp(precursor->get_entity());
385
386                for (size_t i = 0; i < tp.num_paras(); i++)
387                {
388                        const msg::text_plain::para &p = tp.get_para(i);
389                        const std::string qs(p.quote_depth()+1, '>');
390                        for (size_t j = 0; j < p.num_lines(); j++)
391                        {
392                                std::string line = p.get_line(j);
393                                size_t nsp = line.find_last_not_of(" ");
394                                if (nsp == std::string::npos) nsp = 0;
395                                if (nsp + 1 < line.length()) line.erase(nsp+1);
396                                body << qs
397                                     << line
398                                     << "\n";
399                        }
400                }
401
402                am->insert("body", q(body.str(), false));
403
404                return respond_compose(req, am, ngs, ngs);
405        }
406
407        void compose::new_thread(boost::shared_ptr<request> req,
408                                 tlate::data_model::ptr am,
409                                 boost::shared_ptr<db::group> group)
410        {
411                std::string (*const q)(std::string,bool) = html::quote;
412//                std::string (*const p)(std::string) = uri::percent_encode;
413
414                boost::shared_ptr<db::user> usr= req->get_session()->get_user();
415
416                am->insert("subject", "");
417
418                std::list<std::string> grs;
419                grs.push_back(q(group->name(), false));
420
421                am->insert("references", new tlate::empty_value);
422                am->insert("body", "");
423
424                return respond_compose(req, am, grs, grs);
425        }
426
427        void compose::respond_compose(boost::shared_ptr<request> req,
428                                     tlate::data_model::ptr am,
429                                     std::list<std::string> &newsgroups,
430                                     std::list<std::string> &followups)
431        {
432                std::string (*const q)(std::string,bool) = html::quote;
433                //std::string (*const p)(std::string) = uri::percent_encode;
434
435                boost::shared_ptr<db::user> usr= req->get_session()->get_user();
436                am->insert("from", q(usr->get_display_name() +
437                                     " <" +
438                                     usr->get_display_email() +
439                                     ">",
440                                     false));
441
442                am->insert("action", "/compose");
443                am->insert("method", "post");
444                am->insert("enctype",
445                           q("application/x-www-form-urlencoded",
446                             false));
447                am->insert("accept_charlist", q("utf8",false));
448
449                tlate::list_value::ptr grs(new tlate::list_value);
450                for (db::db::group_iterator git = cb.dbase().groups_begin();
451                     git != cb.dbase().groups_end(); git++)
452                {
453                        bool ngsel = std::find(newsgroups.begin(),
454                                               newsgroups.end(),
455                                               git->second->name())
456                                != newsgroups.end();
457                        bool fusel = std::find(followups.begin(),
458                                               followups.end(),
459                                               git->second->name())
460                                != followups.end();
461                        tlate::group_value::ptr gr
462                                (new listed_group_value(git->second, usr,
463                                                        ngsel, fusel));
464                        grs->push_back(gr);
465                }
466                am->insert("grouplist", grs);
467         }
468
469};
470
471namespace
472{
473        class factory : public server::http_resource_factory
474        {
475        public:
476                factory() {
477                        server::register_http_resource("/compose", this);
478                }
479                boost::shared_ptr<http::resource> operator()
480                (server::conn_cb cb, std::string) {
481                        boost::shared_ptr<http::resource> rv
482                                (new http::compose(cb));
483                        return rv;
484                }
485        };
486        factory f;
487}
Note: See TracBrowser for help on using the browser.