/* * File: form.cc * * Copyright 2008 Jorge Arellano Cid * Copyright 2024 Rodrigo Arias Mallo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ #include "form.hh" #include "html_common.hh" #include #include #include "lout/misc.hh" #include "dw/core.hh" #include "dw/textblock.hh" #include "dlib/dlib.hh" #include "misc.hh" #include "msg.hh" #include "prefs.hh" #include "uicmd.hh" #include "dialog.hh" #include #include #include #include #include #include using namespace std::literals::string_literals; using namespace std::literals::string_view_literals; using namespace lout; using namespace dw; using namespace dw::core; using namespace dw::core::style; using namespace dw::core::ui; /* * Forward declarations */ class DilloHtmlReceiver; class DilloHtmlSelect; static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize); static void Html_option_finish(DilloHtml *html); /* * Class declarations */ class DilloHtmlFormImpl : public DilloHtmlForm { friend class DilloHtmlReceiver; friend class DilloHtmlInput; DilloHtml *html; bool showing_hiddens; bool enabled; void eventHandler(Resource *resource, EventButton *event); std::unique_ptr< DilloUrl > buildQueryUrl(DilloHtmlInput *active_input); std::optional< std::string > buildQueryData(DilloHtmlInput *active_submit); char *makeMultipartBoundary(iconv_t char_encoder, DilloHtmlInput *active_submit); std::string encodeText(iconv_t char_encoder, std::string &&input); void strUrlencodeAppend(std::string &dstr, std::string_view str); void inputUrlencodeAppend(std::string &data, std::string_view name, std::string_view value); void inputMultipartAppend(std::string &data, std::string_view boundary, std::string_view name, std::string_view value); void filesInputMultipartAppend(std::string &data, std::string_view boundary, std::string_view name, const std::string &file, std::string_view filename); void imageInputUrlencodeAppend(std::string &data, std::string_view name, std::string_view x, std::string_view y); void imageInputMultipartAppend(std::string &data, std::string_view boundary, std::string_view name, std::string_view x, std::string_view y); public: //BUG: for now everything is public DilloHtmlMethod method; DilloHtmlMethod get_method() const override { return method; } std::unique_ptr< DilloUrl > action; DilloHtmlEnc content_type; DilloHtmlEnc get_content_type() const override { return content_type; } char *submit_charset; std::vector< std::shared_ptr< DilloHtmlInput > > inputs; std::vector< std::shared_ptr< DilloHtmlInput > > *get_inputs() override { return &inputs; } int num_entry_fields; std::unique_ptr< DilloHtmlReceiver > form_receiver; public: DilloHtmlFormImpl (DilloHtml *html, DilloHtmlMethod method, const DilloUrl *action, DilloHtmlEnc content_type, const char *charset, bool enabled); ~DilloHtmlFormImpl () override; std::shared_ptr< DilloHtmlInput > getInput (Resource *resource); std::shared_ptr< DilloHtmlInput > getRadioInput (const char *name); void submit(DilloHtmlInput *active_input, EventButton *event); void reset (); void display_hiddens(bool display); void addInput(std::unique_ptr< DilloHtmlInput > input, DilloHtmlInputType type) override; void setEnabled(bool enabled); }; class DilloHtmlReceiver: public Resource::ActivateReceiver, public Resource::ClickedReceiver { private: friend class DilloHtmlFormImpl; DilloHtmlFormImpl* form; public: DilloHtmlReceiver (DilloHtmlFormImpl* form2) { form = form2; } private: void activate (Resource *resource); void enter (Resource *resource); void leave (Resource *resource); void clicked (Resource *resource, EventButton *event); }; class DilloHtmlInput { // DilloHtmlFormImpl::addInput() calls connectTo() friend class DilloHtmlFormImpl; public: //BUG: for now everything is public DilloHtmlInputType type; Embed *embed; /* May be NULL (think: hidden input) */ std::optional< std::string > name; std::optional< std::string > init_str; /* note: some overloading - for buttons, init_str is simply the value of the button; for text entries, it is the initial value */ std::unique_ptr< DilloHtmlSelect > select; bool init_val; /* only meaningful for buttons */ std::string file_data; /* only meaningful for file inputs. TODO: may become a list... */ private: void connectTo(DilloHtmlReceiver *form_receiver); void activate(DilloHtmlFormImpl *form, int num_entry_fields,EventButton *event); void readFile(BrowserWindow *bw); public: DilloHtmlInput (DilloHtmlInputType type, Embed *embed, const std::optional< std::string > &name, const std::optional< std::string > &init_str, bool init_val); void appendValuesTo(std::vector< std::string > &values, bool is_active_submit); void reset(); void setEnabled(bool enabled) {if (embed) embed->setEnabled(enabled); }; }; class DilloHtmlOptbase { public: virtual ~DilloHtmlOptbase ()= default; virtual bool isSelected() {return false;} virtual bool select() {return false;} virtual const char *getValue() {return NULL;} virtual void setContent(const char *str, int len) {MSG_ERR("Form: Optbase setContent()\n");} virtual void addSelf(SelectionResource *res) = 0; }; class DilloHtmlOptgroup : public DilloHtmlOptbase { private: std::string label; bool enabled; public: DilloHtmlOptgroup (const std::string &label, bool enabled); void addSelf (SelectionResource *res) {res->pushGroup(label, enabled);} }; class DilloHtmlOptgroupClose : public DilloHtmlOptbase { public: virtual ~DilloHtmlOptgroupClose () {}; void addSelf (SelectionResource *res) {res->popGroup();} }; class DilloHtmlOption : public DilloHtmlOptbase { friend class DilloHtmlSelect; public: char *value, *label, *content; bool selected, enabled; DilloHtmlOption (char *value, char *label, bool selected, bool enabled); virtual ~DilloHtmlOption (); bool isSelected() {return selected;} bool select() {return (selected = true);} const char *getValue() {return value ? value : content;} void setContent(const char *str, int len) {content = dStrndup(str, len);} void addSelf (SelectionResource *res) {res->addItem(label ? label : content, enabled, selected);} }; class DilloHtmlSelect { friend class DilloHtmlInput; private: std::vector< std::unique_ptr< DilloHtmlOptbase > > opts; DilloHtmlSelect ()= default; public: DilloHtmlOptbase *getCurrentOpt (); void addOpt (std::unique_ptr< DilloHtmlOptbase > opt); void ensureSelection (); void addOptsTo (SelectionResource *res); void reset (SelectionResource *res); void appendValuesTo (std::vector< std::string > &values, SelectionResource *res); }; /* * Form API */ std::unique_ptr< DilloHtmlForm > a_Html_form_new (DilloHtml *html, DilloHtmlMethod method, const DilloUrl *action, DilloHtmlEnc content_type, const char *charset, bool enabled) { return std::make_unique< DilloHtmlFormImpl > (html, method, action, content_type, charset, enabled); } void a_Html_form_submit2(void *vform) { ((DilloHtmlFormImpl *)vform)->submit(NULL, NULL); } void a_Html_form_reset2(void *vform) { ((DilloHtmlFormImpl *)vform)->reset(); } void a_Html_form_display_hiddens2(void *vform, bool display) { ((DilloHtmlFormImpl *)vform)->display_hiddens(display); } /* * Form parsing functions */ /** * Add an HTML control */ static void Html_add_input(DilloHtml *html, DilloHtmlInputType type, Embed *embed, const std::optional< std::string > &name, const std::optional< std::string > &init_str, bool init_val) { _MSG("name=[%s] init_str=[%s] init_val=[%d]\n", name.value_or( "" ).c_str(), init_str.value_or( "" ).c_str(), init_val); auto input = std::make_unique< DilloHtmlInput >(type, embed, name, init_str, init_val); if (html->InFlags & IN_FORM) { html->getCurrentForm()->addInput( std::move( input ), type ); } else { if (html->bw->NumPendingStyleSheets > 0) { input->setEnabled(false); } html->inputs_outside_form.push_back( std::move( input ) ); } } /** * Find radio input by name */ static std::shared_ptr< DilloHtmlInput > Html_get_radio_input(DilloHtml *html, const char *name) { if (name) { std::vector< std::shared_ptr< DilloHtmlInput > > *inputs; if (html->InFlags & IN_FORM) inputs = html->getCurrentForm()->get_inputs(); else inputs = &html->inputs_outside_form; for (int idx = 0; idx < inputs->size(); idx++) { auto input = inputs->at(idx); if (input->type == DILLO_HTML_INPUT_RADIO && input->name && !dStrAsciiCasecmp(input->name.value().c_str(), name)) return input; } } return nullptr; } /** * Get the current input if available. */ static std::shared_ptr< DilloHtmlInput > Html_get_current_input(DilloHtml &html) { std::vector< std::shared_ptr< DilloHtmlInput > > *inputs; if (html.InFlags & IN_FORM) inputs = html.getCurrentForm()->get_inputs(); else inputs = &html.inputs_outside_form; return (inputs && inputs->size() > 0) ? inputs->at (inputs->size() - 1) : nullptr; } /** * Handle
tag */ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) { std::unique_ptr< DilloUrl > action; DilloHtmlMethod method; DilloHtmlEnc content_type; char *first; std::optional< std::string > charset; const char *attrbuf; HT2TB(html)->addParbreak (9, html->wordStyle ()); if (html->InFlags & IN_FORM) { BUG_MSG("Nested ."); return; } html->InFlags |= IN_FORM; html->InFlags &= ~IN_SELECT; html->InFlags &= ~IN_OPTION; html->InFlags &= ~IN_TEXTAREA; method = DILLO_HTML_METHOD_GET; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "method"))) { if (!dStrAsciiCasecmp(attrbuf, "post")) { method = DILLO_HTML_METHOD_POST; } else if (dStrAsciiCasecmp(attrbuf, "get")) { BUG_MSG(" submission method unknown: '%s'.", attrbuf); } } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action"))) action = a_Html_url_new(html, attrbuf, NULL, 0); else { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) BUG_MSG(" requires action attribute."); action = a_Url_dup(html->base_url.get()); } content_type = DILLO_HTML_ENC_URLENCODED; if ((method == DILLO_HTML_METHOD_POST) && ((attrbuf = a_Html_get_attr(html, tag, tagsize, "enctype")))) { if (!dStrAsciiCasecmp(attrbuf, "multipart/form-data")) content_type = DILLO_HTML_ENC_MULTIPART; } first = NULL; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "accept-charset"))) { /* a list of acceptable charsets, separated by commas or spaces */ char *ptr = first = dStrdup(attrbuf); while (ptr && !charset) { char *curr = dStrsep(&ptr, " ,"); if (!dStrAsciiCasecmp(curr, "utf-8")) { charset = curr; } else if (!dStrAsciiCasecmp(curr, "UNKNOWN")) { /* defined to be whatever encoding the document is in */ charset = html->charset; } } if (!charset) charset = first; } if (!charset) charset = html->charset; html->formNew(method, action.get(), content_type, charset.has_value() ? charset.value().c_str() : nullptr ); dFree(first); } void Html_tag_close_form(DilloHtml *html) { html->InFlags &= ~IN_FORM; html->InFlags &= ~IN_SELECT; html->InFlags &= ~IN_OPTION; html->InFlags &= ~IN_TEXTAREA; } /** * get size, restrict it to reasonable value */ static int Html_input_get_size(DilloHtml *html, const char *attrbuf) { const int MAX_SIZE = 1024; int size = 20; if (attrbuf) { size = strtol(attrbuf, NULL, 10); if (size < 1 || size > MAX_SIZE) { int badSize = size; size = (size < 1 ? 20 : MAX_SIZE); BUG_MSG(" size=%d, using size=%d instead.", badSize, size); } } return size; } /** * Add a new input to current form */ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) { DilloHtmlInputType inp_type; Resource *resource = NULL; Embed *embed = NULL; const char *attrbuf, *label; bool init_val = false; ResourceFactory *factory; if (html->InFlags & IN_SELECT) { BUG_MSG(" inside inside