Files
flenser/src/form.cc

1965 lines
61 KiB
C++

/*
* File: form.cc
*
* Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>
* Copyright 2024 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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 <errno.h>
#include <iconv.h>
#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 <memory>
#include <vector>
#include <string>
#include <optional>
#include <boost/lexical_cast.hpp>
#include <Alepha/AutoRAII.h>
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 <FORM> 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 <form>.");
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("<form> 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("<form> 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("<input> 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("<input> inside <select>.");
return;
}
if (html->InFlags & IN_BUTTON) {
BUG_MSG("<input> inside <button>.");
return;
}
factory = HT2LT(html)->getResourceFactory();
/* Get 'value', 'name' and 'type' */
auto value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
auto name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
auto type = a_Html_get_attr_wdef(html, tag, tagsize, "type", "");
std::optional< std::string > init_str;
std::optional< std::string > placeholder;
inp_type = DILLO_HTML_INPUT_UNKNOWN;
if (!dStrAsciiCasecmp(type.value().c_str(), "password")) {
inp_type = DILLO_HTML_INPUT_PASSWORD;
placeholder = a_Html_get_attr_wdef(html, tag,tagsize,"placeholder",NULL);
attrbuf = a_Html_get_attr(html, tag, tagsize, "size");
int size = Html_input_get_size(html, attrbuf);
resource = factory->createEntryResource (size, true, NULL, placeholder ? placeholder.value().c_str() : nullptr);
init_str = value;
} else if (!dStrAsciiCasecmp(type.value().c_str(), "checkbox")) {
inp_type = DILLO_HTML_INPUT_CHECKBOX;
resource = factory->createCheckButtonResource(false);
init_val = (a_Html_get_attr(html, tag, tagsize, "checked") != NULL);
init_str = value.value_or( "on" );
} else if (!dStrAsciiCasecmp(type.value().c_str(), "radio")) {
inp_type = DILLO_HTML_INPUT_RADIO;
RadioButtonResource *rb_r = NULL;
std::shared_ptr input = Html_get_radio_input(html, name ? name.value().c_str() : nullptr);
if (input)
rb_r = (RadioButtonResource*) input->embed->getResource();
resource = factory->createRadioButtonResource(rb_r, false);
init_val = (a_Html_get_attr(html, tag, tagsize, "checked") != NULL);
init_str = value;
} else if (!dStrAsciiCasecmp(type.value().c_str(), "hidden")) {
inp_type = DILLO_HTML_INPUT_HIDDEN;
init_str = value;
int size = Html_input_get_size(html, NULL);
resource = factory->createEntryResource(size, false, name ? name.value().c_str() : nullptr, NULL);
} else if (!dStrAsciiCasecmp(type.value().c_str(), "submit")) {
inp_type = DILLO_HTML_INPUT_SUBMIT;
init_str = value.value_or("submit");
resource = factory->createLabelButtonResource(init_str.value().c_str());
} else if (!dStrAsciiCasecmp(type.value().c_str(), "reset")) {
inp_type = DILLO_HTML_INPUT_RESET;
init_str = value.value_or( "Reset" );
resource = factory->createLabelButtonResource(init_str.value().c_str());
} else if (!dStrAsciiCasecmp(type.value().c_str(), "image")) {
if (URL_FLAGS(html->base_url) & URL_SpamSafe) {
/* Don't request the image; make a text submit button instead */
inp_type = DILLO_HTML_INPUT_SUBMIT;
attrbuf = a_Html_get_attr(html, tag, tagsize, "alt");
label = attrbuf ? attrbuf : value.value_or( name.value_or( "Submit" ) ).c_str();
init_str = label;
resource = factory->createLabelButtonResource(init_str.value().c_str());
} else {
inp_type = DILLO_HTML_INPUT_IMAGE;
/* use a dw_image widget */
embed = Html_input_image(html, tag, tagsize);
init_str = value;
}
} else if (!dStrAsciiCasecmp(type.value().c_str(), "file")) {
bool valid = true;
if (html->InFlags & IN_FORM) {
DilloHtmlForm *form = html->getCurrentForm();
if (form->get_method() != DILLO_HTML_METHOD_POST) {
valid = false;
BUG_MSG("<form> with file input MUST use HTTP POST method.");
MSG("File input ignored in form not using HTTP POST method\n");
} else if (form->get_content_type() != DILLO_HTML_ENC_MULTIPART) {
valid = false;
BUG_MSG("<form> with file input MUST use multipart/form-data"
" encoding.");
MSG("File input ignored in form not using multipart/form-data"
" encoding\n");
}
}
if (valid) {
inp_type = DILLO_HTML_INPUT_FILE;
init_str = "File selector";
resource = factory->createLabelButtonResource(init_str.value().c_str());
}
} else if (!dStrAsciiCasecmp(type.value().c_str(), "button")) {
inp_type = DILLO_HTML_INPUT_BUTTON;
if (value) {
init_str = value;
resource = factory->createLabelButtonResource(init_str.value().c_str());
}
} else {
/* Text input, which also is the default */
inp_type = DILLO_HTML_INPUT_TEXT;
placeholder = a_Html_get_attr_wdef(html, tag,tagsize,"placeholder",NULL);
attrbuf = a_Html_get_attr(html, tag, tagsize, "size");
int size = Html_input_get_size(html, attrbuf);
resource = factory->createEntryResource(size, false, NULL, placeholder ? placeholder.value().c_str() : nullptr);
init_str = value;
}
if (resource)
embed = new Embed (resource);
if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
Html_add_input(html, inp_type, embed, name, init_str, init_val);
}
if (embed != NULL && inp_type != DILLO_HTML_INPUT_IMAGE &&
inp_type != DILLO_HTML_INPUT_UNKNOWN) {
if (inp_type == DILLO_HTML_INPUT_HIDDEN) {
/* TODO Perhaps do this with access to current form setting */
embed->setDisplayed(false);
}
if (inp_type == DILLO_HTML_INPUT_TEXT ||
inp_type == DILLO_HTML_INPUT_PASSWORD) {
if (a_Html_get_attr(html, tag, tagsize, "readonly"))
((EntryResource *) resource)->setEditable(false);
/* Maximum length of the text in the entry */
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "maxlength"))) {
int maxlen = strtol(attrbuf, NULL, 10);
((EntryResource *) resource)->setMaxLength(maxlen);
}
}
if (prefs.show_tooltip &&
(attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) {
html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,
attrbuf);
}
HT2TB(html)->addWidget (embed, html->backgroundStyle());
}
}
/**
* The ISINDEX tag is just a deprecated form of <INPUT type=text> with
* implied FORM, afaics.
*/
void Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize)
{
std::unique_ptr< DilloUrl > action;
Embed *embed;
const char *attrbuf;
if (html->InFlags & IN_FORM) {
MSG("<isindex> inside <form> not handled.\n");
return;
}
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action")))
action = a_Html_url_new(html, attrbuf, NULL, 0);
else
action = a_Url_dup(html->base_url.get());
html->formNew(DILLO_HTML_METHOD_GET, action.get(), DILLO_HTML_ENC_URLENCODED,
html->charset.has_value() ? html->charset.value().c_str() : nullptr);
html->InFlags |= IN_FORM;
HT2TB(html)->addParbreak (9, html->wordStyle ());
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "prompt")))
HT2TB(html)->addText(attrbuf, html->wordStyle ());
ResourceFactory *factory = HT2LT(html)->getResourceFactory();
EntryResource *entryResource = factory->createEntryResource (20, false,
NULL, NULL);
embed = new Embed (entryResource);
Html_add_input(html, DILLO_HTML_INPUT_INDEX, embed, std::nullopt, std::nullopt, FALSE);
HT2TB(html)->addWidget (embed, html->backgroundStyle ());
html->InFlags &= ~IN_FORM;
}
void Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize)
{
assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);
html->InFlags |= IN_TEXTAREA;
}
/**
* The textarea tag.
*/
void Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize)
{
const int MAX_COLS=1024, MAX_ROWS=10000;
char *name;
const char *attrbuf;
int cols, rows;
a_Html_stash_init(html);
S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cols"))) {
cols = strtol(attrbuf, NULL, 10);
} else {
if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)
BUG_MSG("<textarea> requires cols attribute.");
cols = 20;
}
if (cols < 1 || cols > MAX_COLS) {
int badCols = cols;
cols = (cols < 1 ? 20 : MAX_COLS);
BUG_MSG("<textarea> cols=%d, using cols=%d instead.", badCols, cols);
}
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rows"))) {
rows = strtol(attrbuf, NULL, 10);
} else {
if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)
BUG_MSG("<textarea> requires rows attribute.");
rows = 3;
}
if (rows < 1 || rows > MAX_ROWS) {
int badRows = rows;
rows = (rows < 1 ? 2 : MAX_ROWS);
BUG_MSG("<textarea> rows=%d, using rows=%d instead.", badRows, rows);
}
name = NULL;
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name")))
name = dStrdup(attrbuf);
attrbuf = a_Html_get_attr(html, tag, tagsize, "placeholder");
ResourceFactory *factory = HT2LT(html)->getResourceFactory();
MultiLineTextResource *textres =
factory->createMultiLineTextResource (cols, rows, attrbuf);
Embed *embed = new Embed(textres);
/* Readonly or not? */
if (a_Html_get_attr(html, tag, tagsize, "readonly"))
textres->setEditable(false);
Html_add_input(html, DILLO_HTML_INPUT_TEXTAREA, embed, name, std::nullopt, false);
HT2TB(html)->addWidget (embed, html->backgroundStyle ());
dFree(name);
}
/**
* Close textarea.
* (TEXTAREA is parsed in VERBATIM mode, and entities are handled here)
*/
void Html_tag_close_textarea(DilloHtml *html)
{
int i;
if (html->InFlags & IN_TEXTAREA && !S_TOP(html)->display_none) {
/* Remove the line ending that follows the opening tag */
if (html->Stash[0] == '\r')
html->Stash.erase( 0, 1 );
if (html->Stash[0] == '\n')
html->Stash.erase( 0, 1 );
/* As the spec recommends to canonicalize line endings, it is safe
* to replace '\r' with '\n'. It will be canonicalized anyway! */
for (i = 0; i < html->Stash.size(); ++i) {
if (html->Stash[i] == '\r') {
if (html->Stash[i + 1] == '\n')
html->Stash.erase( i, 1 );
else
html->Stash[i] = '\n';
}
}
/* The HTML3.2 spec says it can have "text and character entities". */
auto str = a_Html_parse_entities(html, html->Stash.c_str(), html->Stash.size());
auto input = Html_get_current_input(*html);
if (input) {
input->init_str = str;
((MultiLineTextResource *)input->embed->getResource ())->setText(str.c_str());
}
}
html->InFlags &= ~IN_TEXTAREA;
}
/*
* <SELECT>
*/
/* The select tag is quite tricky, because of gorpy html syntax. */
void Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize)
{
const char *attrbuf;
int rows = 0;
assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);
html->InFlags |= IN_SELECT;
html->InFlags &= ~IN_OPTION;
auto name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
ResourceFactory *factory = HT2LT(html)->getResourceFactory ();
DilloHtmlInputType type;
SelectionResource *res;
bool multi = a_Html_get_attr(html, tag, tagsize, "multiple") != NULL;
if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) {
rows = strtol(attrbuf, NULL, 10);
if (rows > 100)
rows = 100;
}
if (rows < 1)
rows = multi ? 10 : 1;
if (rows == 1 && multi == false) {
type = DILLO_HTML_INPUT_SELECT;
res = factory->createOptionMenuResource ();
} else {
ListResource::SelectionMode mode;
type = DILLO_HTML_INPUT_SEL_LIST;
mode = multi ? ListResource::SELECTION_MULTIPLE
: ListResource::SELECTION_AT_MOST_ONE;
res = factory->createListResource (mode, rows);
}
Embed *embed = new Embed(res);
if (prefs.show_tooltip &&
(attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) {
html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,
attrbuf);
}
HT2TB(html)->addWidget (embed, html->backgroundStyle ());
Html_add_input(html, type, embed, name, std::nullopt, false);
a_Html_stash_init(html);
}
/*
* ?
*/
void Html_tag_close_select(DilloHtml *html)
{
if (html->InFlags & IN_SELECT) {
if (html->InFlags & IN_OPTION)
Html_option_finish(html);
html->InFlags &= ~IN_SELECT;
html->InFlags &= ~IN_OPTION;
auto input = Html_get_current_input(*html);
if (input) {
DilloHtmlSelect *select = input->select.get();
if (input->type == DILLO_HTML_INPUT_SELECT) {
// option menu interface requires that something be selected */
select->ensureSelection ();
}
select->addOptsTo ((SelectionResource*)input->embed->getResource());
}
}
}
void Html_tag_open_optgroup(DilloHtml *html, const char *tag, int tagsize)
{
if (!(html->InFlags & IN_SELECT)) {
BUG_MSG("<optgroup> outside <select>.");
return;
}
if (html->InFlags & IN_OPTGROUP) {
BUG_MSG("Nested <optgroup>.");
return;
}
if (html->InFlags & IN_OPTION) {
Html_option_finish(html);
html->InFlags &= ~IN_OPTION;
}
html->InFlags |= IN_OPTGROUP;
auto input = Html_get_current_input(*html);
if (input &&
(input->type == DILLO_HTML_INPUT_SELECT ||
input->type == DILLO_HTML_INPUT_SEL_LIST)) {
auto label = a_Html_get_attr_wdef(html, tag, tagsize, "label", NULL);
bool enabled = (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL);
if (!label) {
BUG_MSG("<optgroup> requires label attribute.");
label = "";
}
auto opt= std::make_unique< DilloHtmlOptgroup >( label.value(), enabled );
input->select->addOpt( std::move( opt ) );
}
}
void Html_tag_close_optgroup(DilloHtml *html)
{
if (html->InFlags & IN_OPTGROUP) {
html->InFlags &= ~IN_OPTGROUP;
if (html->InFlags & IN_OPTION) {
Html_option_finish(html);
html->InFlags &= ~IN_OPTION;
}
auto input = Html_get_current_input(*html);
if (input &&
(input->type == DILLO_HTML_INPUT_SELECT ||
input->type == DILLO_HTML_INPUT_SEL_LIST)) {
auto opt= std::make_unique< DilloHtmlOptgroupClose >();
input->select->addOpt( std::move( opt ) );
}
}
}
/*
* <OPTION>
*/
void Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize)
{
if (!(html->InFlags & IN_SELECT)) {
BUG_MSG("<option> outside <select>.");
return;
}
if (html->InFlags & IN_OPTION)
Html_option_finish(html);
html->InFlags |= IN_OPTION;
auto input = Html_get_current_input(*html);
if (input &&
(input->type == DILLO_HTML_INPUT_SELECT ||
input->type == DILLO_HTML_INPUT_SEL_LIST)) {
auto value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
auto label = a_Html_get_attr_wdef(html, tag, tagsize, "label", NULL);
bool selected = (a_Html_get_attr(html, tag, tagsize,"selected") != NULL);
bool enabled = (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL);
auto option =
std::make_unique< DilloHtmlOption >(value ? dStrdup( value.value().c_str() ).ptr : nullptr, label ? dStrdup( label.value().c_str() ).ptr : nullptr, selected, enabled );
input->select->addOpt( std::move( option ) );
}
a_Html_stash_init(html);
}
void Html_tag_close_option(DilloHtml *html)
{
if (html->InFlags & IN_OPTION) {
Html_option_finish(html);
html->InFlags &= ~IN_OPTION;
}
}
/*
* <BUTTON>
*/
void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize)
{
/*
* Buttons are rendered on one line, this is (at several levels) a
* bit simpler. May be changed in the future.
*/
DilloHtmlInputType inp_type;
assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);
html->InFlags |= IN_BUTTON;
auto type = a_Html_get_attr_wdef(html, tag, tagsize, "type", "");
if (!dStrAsciiCasecmp(type.value().c_str(), "button")) {
inp_type = DILLO_HTML_INPUT_BUTTON;
} else if (!dStrAsciiCasecmp(type.value().c_str(), "reset")) {
inp_type = DILLO_HTML_INPUT_BUTTON_RESET;
} else if (!dStrAsciiCasecmp(type.value().c_str(), "submit") || type.value().empty()) {
/* submit button is the default */
inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT;
} else {
inp_type = DILLO_HTML_INPUT_UNKNOWN;
BUG_MSG("<button> type unknown: '%s'.", type.value().c_str());
}
if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
/* Render the button */
Embed *embed;
const char *attrbuf;
if (prefs.show_tooltip &&
(attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) {
html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,
attrbuf);
}
/* We used to have Textblock (prefs.limit_text_width, ...) here,
* but it caused 100% CPU usage.
*/
std::unique_ptr< Widget > page = std::make_unique< Textblock >( false, true );
Widget *page_p= page.get();
page->setStyle (html->backgroundStyle ());
ResourceFactory *factory = HT2LT(html)->getResourceFactory();
Resource *resource = factory->createComplexButtonResource(std::move( page ), true);
embed = new Embed(resource);
// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE);
HT2TB(html)->addWidget (embed, html->backgroundStyle ());
S_TOP(html)->textblock = html->dw = page_p;
auto value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
auto name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
Html_add_input(html, inp_type, embed, name, value, FALSE);
}
}
/**
* Handle close <BUTTON>
*/
void Html_tag_close_button(DilloHtml *html)
{
html->InFlags &= ~IN_BUTTON;
}
/*
* Class implementations
*/
/*
* DilloHtmlForm
*/
/*
* Constructor
*/
DilloHtmlFormImpl::DilloHtmlFormImpl (DilloHtml *html2,
DilloHtmlMethod method2,
const DilloUrl *action2,
DilloHtmlEnc content_type2,
const char *charset, bool enabled)
{
html = html2;
method = method2;
action = a_Url_dup(action2);
content_type = content_type2;
submit_charset = dStrdup(charset);
num_entry_fields = 0;
showing_hiddens = false;
this->enabled = enabled;
form_receiver= std::make_unique< DilloHtmlReceiver >( this );
}
/*
* Destructor
*/
DilloHtmlFormImpl::~DilloHtmlFormImpl ()
{
dFree(submit_charset);
inputs.clear();
}
void DilloHtmlFormImpl::eventHandler(Resource *resource, EventButton *event)
{
_MSG("DilloHtmlFormImpl::eventHandler\n");
if (event && (event->button == 3)) {
a_UIcmd_form_popup(html->bw, html->page_url.get(), this, showing_hiddens);
} else {
auto input = getInput(resource);
if (input) {
input->activate (this, num_entry_fields, event);
} else {
MSG("DilloHtmlFormImpl::eventHandler: ERROR, input not found!\n");
}
}
}
/*
* Submit.
* (Called by eventHandler())
*/
void DilloHtmlFormImpl::submit(DilloHtmlInput *active_input, EventButton *event)
{
if (!dStrAsciiCasecmp(URL_SCHEME(html->page_url), "https") &&
dStrAsciiCasecmp(URL_SCHEME(action), "https")) {
int choice = a_Dialog_choice("Flenser: Insecure form submission",
"A form on a SECURE page wants to use an "
"INSECURE protocol to submit data.",
"Continue", "Cancel", NULL);
if (choice != 1)
return;
}
auto url = buildQueryUrl(active_input);
if (url) {
if (event && event->button == 2) {
if (prefs.middle_click_opens_new_tab) {
int focus = prefs.focus_new_tab ? 1 : 0;
if (event->state == SHIFT_MASK) focus = !focus;
a_UIcmd_open_url_nt(html->bw, url.get(), focus);
} else {
a_UIcmd_open_url_nw(html->bw, url.get());
}
} else {
a_UIcmd_open_url(html->bw, url.get());
}
}
}
/**
* Build a new query URL.
* (Called by submit())
*/
std::unique_ptr< DilloUrl > DilloHtmlFormImpl::buildQueryUrl(DilloHtmlInput *active_input)
{
std::unique_ptr< DilloUrl > new_url;
if ((method == DILLO_HTML_METHOD_GET) ||
(method == DILLO_HTML_METHOD_POST)) {
std::string DataStr;
DilloHtmlInput *active_submit = NULL;
_MSG("DilloHtmlFormImpl::buildQueryUrl: action=%s\n",URL_STR_(action));
if (active_input) {
if ((active_input->type == DILLO_HTML_INPUT_SUBMIT) ||
(active_input->type == DILLO_HTML_INPUT_IMAGE) ||
(active_input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT)) {
active_submit = active_input;
}
}
auto queryStr= buildQueryData(active_submit);
if (queryStr.has_value()) {
auto DataStr= queryStr.value();
/* action was previously resolved against base URL */
char *action_str = dStrdup(URL_STR(action.get()));
if (method == DILLO_HTML_METHOD_POST) {
new_url = a_Url_new(action_str, NULL);
/* new_url keeps the dStr and sets DataStr to NULL */
a_Url_set_data(new_url.get(), DataStr);
a_Url_set_flags(new_url.get(), URL_FLAGS(new_url.get()) | URL_Post);
if (content_type == DILLO_HTML_ENC_MULTIPART)
a_Url_set_flags(new_url.get(), URL_FLAGS(new_url.get()) | URL_MultipartEnc);
} else {
/* remove <fragment> and <query> sections if present */
char *url_str, *p;
if ((p = strchr(action_str, '#')))
*p = 0;
if ((p = strchr(action_str, '?')))
*p = 0;
url_str = dStrconcat(action_str, "?", DataStr.c_str(), NULL);
new_url = a_Url_new(url_str, NULL);
a_Url_set_flags(new_url.get(), URL_FLAGS(new_url.get()) | URL_Get);
dFree(url_str);
}
dFree(action_str);
}
} else {
MSG("DilloHtmlFormImpl::buildQueryUrl: Method unknown\n");
}
return new_url;
}
/**
* Construct the data for a query URL
*/
std::optional< std::string > DilloHtmlFormImpl::buildQueryData(DilloHtmlInput *active_submit)
{
std::string DataStr;
char *boundary = nullptr;
iconv_t char_encoder = (iconv_t) -1;
if (submit_charset && dStrAsciiCasecmp(submit_charset, "UTF-8")) {
/* Some iconv implementations, given "//TRANSLIT", will do their best to
* transliterate the string. Under the circumstances, doing so is likely
* for the best.
*/
char *translit = dStrconcat(submit_charset, "//TRANSLIT", NULL);
char_encoder = iconv_open(translit, "UTF-8");
dFree(translit);
if (char_encoder == (iconv_t) -1)
char_encoder = iconv_open(submit_charset, "UTF-8");
if (char_encoder == (iconv_t) -1) {
MSG_WARN("Cannot convert to character encoding '%s'\n",
submit_charset);
} else {
MSG("Form character encoding: '%s'\n", submit_charset);
}
}
if (content_type == DILLO_HTML_ENC_MULTIPART) {
if (!(boundary = makeMultipartBoundary(char_encoder, active_submit)))
MSG_ERR("Cannot generate multipart/form-data boundary.\n");
}
if ((content_type == DILLO_HTML_ENC_URLENCODED) || (boundary != NULL)) {
std::vector< std::string > values;
for (int i = 0; i < inputs.size(); i++) {
auto input = inputs.at (i);
std::string name= input->name.value_or( "" );
bool is_active_submit = (input.get() == active_submit);
int valcount;
name = encodeText(char_encoder, std::move( name ));
input->appendValuesTo(values, is_active_submit);
if ((valcount = values.size()) > 0) {
if (input->type == DILLO_HTML_INPUT_FILE) {
if (valcount > 1)
MSG_WARN("multiple files per form control not supported\n");
auto file= values.front();
values.erase( begin( values ) );
/* Get filename and encode it. Do not encode file contents. */
LabelButtonResource *lbr =
(LabelButtonResource*) input->embed->getResource();
const char *filename = lbr->getLabel();
if (filename[0] && strcmp(filename, input->init_str.value().c_str())) {
const char *p = strrchr(filename, '/');
if (p)
filename = p + 1; /* don't reveal path */
std::string dfilename = filename;
dfilename = encodeText(char_encoder, std::move( dfilename ));
filesInputMultipartAppend(DataStr, boundary, name.c_str(),
file, dfilename.c_str());
}
} else if (input->type == DILLO_HTML_INPUT_INDEX) {
/* no name */
auto val= std::move( values.at( 0 ) );
values.erase( begin( values ) );
val = encodeText(char_encoder, std::move( val ));
strUrlencodeAppend(DataStr, val.c_str());
} else if (input->type == DILLO_HTML_INPUT_IMAGE) {
auto x= std::move( values.at( 0 ) );
values.erase( begin( values ) );
auto y= std::move( values.at( 0 ) );
values.erase( begin( values ) );
if (content_type == DILLO_HTML_ENC_URLENCODED)
imageInputUrlencodeAppend(DataStr, name, x, y);
else if (content_type == DILLO_HTML_ENC_MULTIPART)
imageInputMultipartAppend(DataStr, boundary, name, x, y);
} else {
for (int j = 0; j < valcount; j++) {
std::string val= std::move( values.at( 0 ) );
values.erase( begin( values ) );
val = encodeText(char_encoder, std::move( val ));
if (content_type == DILLO_HTML_ENC_URLENCODED)
inputUrlencodeAppend(DataStr, name, val);
else if (content_type == DILLO_HTML_ENC_MULTIPART)
inputMultipartAppend(DataStr, boundary, name,
val);
}
}
}
}
if( not DataStr.empty() ) {
if (content_type == DILLO_HTML_ENC_URLENCODED) {
if (DataStr.at( DataStr.size() - 1 ) == '&')
DataStr.pop_back();
} else if (content_type == DILLO_HTML_ENC_MULTIPART) {
DataStr+= "--";
}
}
}
dFree(boundary);
if (char_encoder != (iconv_t) -1)
(void)iconv_close(char_encoder);
return DataStr;
}
/**
* Generate a random boundary.
*
* Using 70 random characters makes the probability that it collides
* with a 1 TiB random file less than 1e-117, so there is no need for
* checking for collisions. */
static void generate_boundary(Dstr *boundary)
{
/* Extracted from RFC 2046, section 5.1.1. */
static const char set[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
static const size_t n = strlen(set);
for (int i = 0; i < 70; i++) {
int c = (unsigned char) set[rand() % n];
dStr_append_c(boundary, c);
}
}
/**
* Generate a boundary string for use in separating the parts of a
* multipart/form-data submission.
*/
char *DilloHtmlFormImpl::makeMultipartBoundary(iconv_t char_encoder,
DilloHtmlInput *active_submit)
{
std::vector< std::string > values;
Dstr *DataStr = dStr_new("");
Dstr *boundary = dStr_new("");
char *ret = NULL;
/* fill DataStr with names, filenames, and values */
for (int i = 0; i < inputs.size(); i++) {
auto input = inputs.at (i);
bool is_active_submit = (input.get() == active_submit);
input->appendValuesTo(values, is_active_submit);
if (input->name.has_value()) {
std::string dstr= input->name.value();
dstr = encodeText(char_encoder, std::move( dstr ));
dStr_append_l(DataStr, dstr.c_str(), dstr.size());
}
if (input->type == DILLO_HTML_INPUT_FILE) {
LabelButtonResource *lbr =
(LabelButtonResource*)input->embed->getResource();
const char *filename = lbr->getLabel();
if (filename[0] && strcmp(filename, input->init_str.value().c_str())) {
std::string dstr = filename;
dstr = encodeText(char_encoder, std::move( dstr ));
dStr_append_l(DataStr, dstr.c_str(), dstr.size());
}
}
int length = values.size();
for (int i = 0; i < length; i++) {
auto dstr= std::move( values.at( 0 ) );
values.erase( begin( values ) );
if (input->type != DILLO_HTML_INPUT_FILE)
dstr = encodeText(char_encoder, std::move( dstr ));
dStr_append_l(DataStr, dstr.c_str(), dstr.size());
}
}
generate_boundary(boundary);
ret = boundary->str;
dStr_free(DataStr, 1);
dStr_free(boundary, (ret == NULL));
return ret;
}
/**
* Pass input text through character set encoder.
* Return value: same input Dstr if no encoding is needed.
* new Dstr when encoding (input Dstr is freed).
*/
std::string DilloHtmlFormImpl::encodeText(iconv_t char_encoder, std::string &&input)
{
int rc = 0;
const int bufsize = 128;
inbuf_t *inPtr;
char *outPtr;
size_t inLeft, outRoom;
bool bad_chars = false;
if ((char_encoder == (iconv_t) -1) || input.empty())
return input;
std::string output;
inPtr = input.data();
inLeft = input.size();
std::vector< char > buffer( bufsize );
while ((rc != EINVAL) && (inLeft > 0)) {
outPtr = buffer.data();
outRoom = bufsize;
rc = iconv(char_encoder, &inPtr, &inLeft, &outPtr, &outRoom);
// iconv() on success, number of bytes converted
// -1, errno == EILSEQ illegal byte sequence found
// EINVAL partial character ends source buffer
// E2BIG destination buffer is full
//
// GNU iconv has the undocumented(!) behavior that EILSEQ is also
// returned when a character cannot be converted.
output+= std::string_view{ buffer.data(), buffer.data() + bufsize - outRoom };
if (rc == -1) {
rc = errno;
}
if (rc == EILSEQ){
/* count chars? (would be utf-8-specific) */
bad_chars = true;
inPtr++;
inLeft--;
output+= '?';
} else if (rc == EINVAL) {
MSG_ERR("Form encode text: bad source string.\n");
}
}
if (bad_chars) {
/*
* It might be friendly to inform the caller, who would know whether
* it is safe to display the beginning of the string in a message
* (isn't, e.g., a password).
*/
MSG_WARN("Form encode text: string cannot be converted cleanly.\n");
}
return output;
}
/**
* Urlencode 'str' and append it to 'dstr'
*/
void DilloHtmlFormImpl::strUrlencodeAppend(std::string &dstr, std::string_view str_)
{
const std::string str{ str_ };
auto encoded= a_Url_encode_hex_str(str.c_str()).value();
dstr+= encoded;
}
/**
* Append a name-value pair to url data using url encoding.
*/
void DilloHtmlFormImpl::inputUrlencodeAppend(std::string &data, const std::string_view name,
const std::string_view value)
{
if (not name.empty()) {
strUrlencodeAppend(data, name);
data+= '=';
strUrlencodeAppend(data, value);
data+= '&';
}
}
/**
* Append files to URL data using multipart encoding.
* Currently only accepts one file.
*/
void DilloHtmlFormImpl::filesInputMultipartAppend(std::string &data,
std::string_view boundary,
std::string_view name,
const std::string &file,
std::string_view filename_)
{
const char *ctype, *ext;
if (not name.empty()) {
const std::string filename{ filename_ };
(void)a_Misc_get_content_type_from_data(const_cast< char * >( file.data() ), file.size(), &ctype);
/* Heuristic: text/plain with ".htm[l]" extension -> text/html */
if ((ext = strrchr(filename.c_str(), '.')) &&
!dStrAsciiCasecmp(ctype, "text/plain") &&
(!dStrAsciiCasecmp(ext, ".html") || !dStrAsciiCasecmp(ext, ".htm"))){
ctype = "text/html";
}
if (data.empty()) {
data+= "--";
data+= boundary;
}
data+= "\r\n"s
+ "Content-Disposition: form-data; name=\"" + std::string{ name } +"\"; "
+ "filename=\"";
/*
* Replace the characters that are the most likely to damage things.
* For a while, there was some momentum to standardize on an encoding,
* but HTML5/Ian Hickson/his Google masters are, as of late 2012,
* evidently standing in opposition to all of that for some reason.
*/
for (int i = 0; char c = filename.at(i); i++) {
if (c == '\"' || c == '\r' || c == '\n')
c = '_';
data+= c;
}
data+= "\"\r\n"s
+ "Content-Type: " + ctype + "\r\n"
+ "\r\n";
data+= std::string_view{ file.c_str(), file.c_str() + file.size() };
data+= "\r\n--"s + std::string{ boundary };
}
}
/**
* Append a name-value pair to url data using multipart encoding.
*/
void DilloHtmlFormImpl::inputMultipartAppend(std::string &data,
std::string_view boundary,
std::string_view name,
std::string_view value)
{
if (not name.empty()) {
if (data.empty()) {
data+= "--";
data+= boundary;
}
data+=
"\r\n"s
+ "Content-Disposition: form-data; name=\"" + std::string{ name } + "\"\r\n"
+ "\r\n"
+ std::string{ value } + "\r\n"
+ "--"
+ std::string{ boundary };
}
}
/**
* Append an image button click position to url data using url encoding.
*/
void DilloHtmlFormImpl::imageInputUrlencodeAppend(std::string &data, const std::string_view name, const std::string_view x,
const std::string_view y)
{
if (not name.empty()) {
strUrlencodeAppend(data, name);
data+= ".x="s;
data+= x;
data+= "&";
strUrlencodeAppend(data, name);
data+= ".y="s;
data+= y;
data+= "&";
} else
data+= "x="s + std::string{ x } + "&y=" + std::string{ y } + "&";
}
/**
* Append an image button click position to url data using multipart encoding.
*/
void DilloHtmlFormImpl::imageInputMultipartAppend(std::string &data, const std::string_view boundary,
const std::string_view name_, const std::string_view x_, const std::string_view y_)
{
std::string name{ name_ };
const std::string x{ x_ };
const std::string y{ y_ };
int orig_len = name.size();
if (orig_len)
name+= '.';
name+= 'x';
inputMultipartAppend(data, boundary, name.c_str(), x.c_str());
name.pop_back();
name+= 'y';
inputMultipartAppend(data, boundary, name.c_str(), y.c_str());
}
/**
* Reset all inputs containing reset to their initial values. In
* general, reset is the reset button for the form.
*/
void DilloHtmlFormImpl::reset ()
{
int size = inputs.size();
for (int i = 0; i < size; i++)
inputs.at(i)->reset();
}
/**
* Show/hide "hidden" form controls
*/
void DilloHtmlFormImpl::display_hiddens(bool display)
{
int size = inputs.size();
for (int i = 0; i < size; i++) {
auto input = inputs.at(i);
if (input->type == DILLO_HTML_INPUT_HIDDEN) {
input->embed->setDisplayed(display);
}
}
showing_hiddens = display;
}
void DilloHtmlFormImpl::setEnabled(bool enabled)
{
for (int i = 0; i < inputs.size(); i++)
inputs.at(i)->setEnabled(enabled);
}
/**
* Add a new input.
*/
void DilloHtmlFormImpl::addInput(std::unique_ptr< DilloHtmlInput > input, DilloHtmlInputType type)
{
input->connectTo (form_receiver.get());
input->setEnabled (enabled);
inputs.push_back( std::move( input ) );
/* some stats */
if (type == DILLO_HTML_INPUT_PASSWORD ||
type == DILLO_HTML_INPUT_TEXT) {
num_entry_fields++;
}
}
/**
* Return the input with a given resource.
*/
std::shared_ptr< DilloHtmlInput > DilloHtmlFormImpl::getInput (Resource *resource)
{
for (int idx = 0; idx < inputs.size(); idx++) {
auto input = inputs.at(idx);
if (input->embed &&
resource == input->embed->getResource())
return input;
}
return nullptr;
}
/**
* Return a Radio input for the given name.
*/
std::shared_ptr< DilloHtmlInput > DilloHtmlFormImpl::getRadioInput (const char *name)
{
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;
}
/*
* DilloHtmlReceiver
*
* TODO: Currently there's "clicked" for buttons, we surely need "enter" for
* textentries, and maybe the "mouseover, ...." set for Javascript.
*/
void DilloHtmlReceiver::activate (Resource *resource)
{
form->eventHandler(resource, NULL);
}
/**
* Enter a form control, as in "onmouseover".
* For _pressing_ enter in a text control, see activate().
*/
void DilloHtmlReceiver::enter (Resource *resource)
{
DilloHtml *html = form->html;
auto input = form->getInput(resource);
const char *msg = "";
if ((input->type == DILLO_HTML_INPUT_SUBMIT) ||
(input->type == DILLO_HTML_INPUT_IMAGE) ||
(input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT) ||
(input->type == DILLO_HTML_INPUT_INDEX) ||
((prefs.enterpress_forces_submit || form->num_entry_fields == 1) &&
((input->type == DILLO_HTML_INPUT_PASSWORD) ||
(input->type == DILLO_HTML_INPUT_TEXT)))) {
/* The control can submit form. Show action URL. */
msg = URL_STR(form->action.get());
}
a_UIcmd_set_msg(html->bw, "%s", msg);
}
/**
* Leave a form control, or "onmouseout".
*/
void DilloHtmlReceiver::leave (Resource *resource)
{
DilloHtml *html = form->html;
a_UIcmd_set_msg(html->bw, "");
}
void DilloHtmlReceiver::clicked (Resource *resource,
EventButton *event)
{
form->eventHandler(resource, event);
}
/*
* DilloHtmlInput
*/
/*
* Constructor
*/
DilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, Embed *embed2,
const std::optional< std::string > &name2, const std::optional< std::string > &init_str2,
bool init_val2)
{
type = type2;
embed = embed2;
name = name2;
init_str = init_str2;
init_val = init_val2;
select = NULL;
switch (type) {
case DILLO_HTML_INPUT_SELECT:
case DILLO_HTML_INPUT_SEL_LIST:
select.reset( new DilloHtmlSelect );
break;
default:
break;
}
reset ();
}
/**
* Connect to a receiver.
*/
void DilloHtmlInput::connectTo(DilloHtmlReceiver *form_receiver)
{
Resource *resource;
if (embed && (resource = embed->getResource())) {
resource->connectClicked (form_receiver);
if (type == DILLO_HTML_INPUT_SUBMIT ||
type == DILLO_HTML_INPUT_RESET ||
type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
type == DILLO_HTML_INPUT_BUTTON_RESET ||
type == DILLO_HTML_INPUT_IMAGE ||
type == DILLO_HTML_INPUT_FILE ||
type == DILLO_HTML_INPUT_TEXT ||
type == DILLO_HTML_INPUT_PASSWORD ||
type == DILLO_HTML_INPUT_INDEX) {
resource->connectActivate (form_receiver);
}
}
}
/**
* Activate a form
*/
void DilloHtmlInput::activate(DilloHtmlFormImpl *form, int num_entry_fields,
EventButton *event)
{
switch (type) {
case DILLO_HTML_INPUT_FILE:
readFile (form->html->bw);
break;
case DILLO_HTML_INPUT_RESET:
case DILLO_HTML_INPUT_BUTTON_RESET:
form->reset();
break;
case DILLO_HTML_INPUT_TEXT:
case DILLO_HTML_INPUT_PASSWORD:
if (!(prefs.enterpress_forces_submit || num_entry_fields == 1)) {
break;
}
/* fallthrough */
case DILLO_HTML_INPUT_SUBMIT:
case DILLO_HTML_INPUT_BUTTON_SUBMIT:
case DILLO_HTML_INPUT_IMAGE:
case DILLO_HTML_INPUT_INDEX:
form->submit(this, event);
break;
default:
break;
}
}
/**
* Read a file into cache
*/
void DilloHtmlInput::readFile (BrowserWindow *bw)
{
const char *filename = a_UIcmd_select_file();
if (filename) {
a_UIcmd_set_msg(bw, "Loading file...");
file_data = a_Misc_file2dstr(filename);
if (not file_data.empty()) {
a_UIcmd_set_msg(bw, "File loaded.");
LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
lbr->setLabel(filename);
} else {
a_UIcmd_set_msg(bw, "ERROR: can't load: %s", filename);
}
}
}
/**
* Get the values for a "successful control".
*/
void DilloHtmlInput::appendValuesTo(std::vector< std::string > &values, bool is_active_submit)
{
switch (type) {
case DILLO_HTML_INPUT_TEXT:
case DILLO_HTML_INPUT_PASSWORD:
case DILLO_HTML_INPUT_INDEX:
case DILLO_HTML_INPUT_HIDDEN:
{
EntryResource *entryres = (EntryResource*)embed->getResource();
values.push_back(entryres->getText());
}
break;
case DILLO_HTML_INPUT_TEXTAREA:
{
MultiLineTextResource *textres =
(MultiLineTextResource*)embed->getResource();
values.push_back(textres->getText());
}
break;
case DILLO_HTML_INPUT_CHECKBOX:
case DILLO_HTML_INPUT_RADIO:
{
ToggleButtonResource *cb_r =
(ToggleButtonResource*)embed->getResource();
if (name && init_str && cb_r->isActivated()) {
values.push_back( init_str.value() );
}
}
break;
case DILLO_HTML_INPUT_SUBMIT:
case DILLO_HTML_INPUT_BUTTON_SUBMIT:
if (is_active_submit)
values.push_back( init_str.value_or( "" ) );
break;
case DILLO_HTML_INPUT_SELECT:
case DILLO_HTML_INPUT_SEL_LIST:
{
SelectionResource *sel_res = (SelectionResource*)embed->getResource();
select->appendValuesTo (values, sel_res);
}
break;
case DILLO_HTML_INPUT_FILE:
{
LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
const char *filename = lbr->getLabel();
if (filename[0] && strcmp(filename, init_str.value().c_str())) {
if (not file_data.empty()) {
values.push_back( file_data );
} else {
MSG("FORM file input \"%s\" not loaded.\n", filename);
}
}
}
break;
case DILLO_HTML_INPUT_IMAGE:
if (is_active_submit) {
ComplexButtonResource *cbr =
(ComplexButtonResource*)embed->getResource();
values.push_back( boost::lexical_cast< std::string >( cbr->getClickX() ) );
values.push_back( boost::lexical_cast< std::string >( cbr->getClickY() ) );
}
break;
default:
break;
}
}
/**
* Reset to the initial value.
*/
void DilloHtmlInput::reset ()
{
switch (type) {
case DILLO_HTML_INPUT_TEXT:
case DILLO_HTML_INPUT_PASSWORD:
case DILLO_HTML_INPUT_INDEX:
case DILLO_HTML_INPUT_HIDDEN:
{
EntryResource *entryres = (EntryResource*)embed->getResource();
entryres->setText(init_str.value_or( "" ).c_str());
}
break;
case DILLO_HTML_INPUT_CHECKBOX:
case DILLO_HTML_INPUT_RADIO:
{
ToggleButtonResource *tb_r =
(ToggleButtonResource*)embed->getResource();
tb_r->setActivated(init_val);
}
break;
case DILLO_HTML_INPUT_SELECT:
case DILLO_HTML_INPUT_SEL_LIST:
if (select != NULL) {
SelectionResource *sr = (SelectionResource *) embed->getResource();
select->reset(sr);
}
break;
case DILLO_HTML_INPUT_TEXTAREA:
if (init_str.has_value()) {
MultiLineTextResource *textres =
(MultiLineTextResource*)embed->getResource();
textres->setText(init_str.value().c_str());
}
break;
case DILLO_HTML_INPUT_FILE:
{
LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();
lbr->setLabel(init_str.value().c_str());
}
break;
default:
break;
}
}
/*
* DilloHtmlSelect
*/
DilloHtmlOptbase *
DilloHtmlSelect::getCurrentOpt()
{
return opts.back().get();
}
void
DilloHtmlSelect::addOpt( std::unique_ptr< DilloHtmlOptbase > opt )
{
opts.push_back( std::move( opt ) );
}
/**
* Select the first option if nothing else is selected.
*/
void DilloHtmlSelect::ensureSelection()
{
int size = opts.size ();
if (size > 0) {
for (int i = 0; i < size; i++) {
DilloHtmlOptbase *opt = opts.at (i).get();
if (opt->isSelected())
return;
}
for (int i = 0; i < size; i++) {
DilloHtmlOptbase *opt = opts.at (i).get();
if (opt->select())
break;
}
}
}
void DilloHtmlSelect::addOptsTo (SelectionResource *res)
{
int size = opts.size ();
for (int i = 0; i < size; i++) {
DilloHtmlOptbase *opt = opts.at (i).get();
opt->addSelf(res);
}
}
void DilloHtmlSelect::reset (SelectionResource *res)
{
int size = opts.size ();
for (int i = 0; i < size; i++) {
DilloHtmlOptbase *opt = opts.at (i).get();
res->setItem(i, opt->isSelected());
}
}
void DilloHtmlSelect::appendValuesTo (std::vector< std::string > &values, SelectionResource *res)
{
int size = opts.size ();
for (int i = 0; i < size; i++) {
if (res->isSelected (i)) {
DilloHtmlOptbase *opt = opts.at (i).get();
const char *val = opt->getValue();
if (val)
values.push_back( val );
}
}
}
DilloHtmlOptgroup::DilloHtmlOptgroup (const std::string &label, bool enabled)
{
this->label = label;
this->enabled = enabled;
}
/*
* DilloHtmlOption
*/
/*
* Constructor
*/
DilloHtmlOption::DilloHtmlOption (char *value2, char *label2, bool selected2,
bool enabled2)
{
value = value2;
label = label2;
content = NULL;
selected = selected2;
enabled = enabled2;
}
/*
* Destructor
*/
DilloHtmlOption::~DilloHtmlOption ()
{
dFree(value);
dFree(label);
dFree(content);
}
/*
* Utilities
*/
/**
* Create input image for the form
*/
static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize)
{
std::shared_ptr< DilloImage > Image;
Embed *button = NULL;
html->styleEngine->setPseudoLink ();
/* create new image and add it to the button */
a_Html_common_image_attrs(html, tag, tagsize);
if ((Image = a_Html_image_new(html, tag, tagsize))) {
// At this point, we know that Image->ir represents an image
// widget. Notice that the order of the casts matters, because
// of multiple inheritance.
dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr;
std::unique_ptr< dw::core::Widget > dwi_unique;
dwi_unique.reset( dwi );
dwi->setStyle (html->backgroundStyle ());
ResourceFactory *factory = HT2LT(html)->getResourceFactory();
ComplexButtonResource *complex_b_r =
factory->createComplexButtonResource(std::move( dwi_unique ), false);
button = new Embed(complex_b_r);
HT2TB(html)->addWidget (button, html->style ());
}
if (!button)
MSG("Html_input_image: unable to create image submit.\n");
return button;
}
/*
* ?
*/
static void Html_option_finish(DilloHtml *html)
{
auto input = Html_get_current_input(*html);
if (input &&
(input->type == DILLO_HTML_INPUT_SELECT ||
input->type == DILLO_HTML_INPUT_SEL_LIST)) {
DilloHtmlOptbase *opt = input->select->getCurrentOpt ();
opt->setContent (html->Stash.c_str(), html->Stash.size());
}
}