1965 lines
61 KiB
C++
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());
|
|
}
|
|
}
|