From 2cb96c840519df9dcf9ba867b5922322c580c36a Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Thu, 14 Jan 2016 20:27:27 +0100 Subject: [PATCH 01/14] geniuspaste: Fix a memory leak --- geniuspaste/src/geniuspaste.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 97fc09ff7..732d340d6 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -111,6 +111,8 @@ static void load_settings(void) { GKeyFile *config = g_key_file_new(); + if (config_file) + g_free(config_file); config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S, "geniuspaste", G_DIR_SEPARATOR_S, "geniuspaste.conf", NULL); g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); From 38bcf4696da67f1244691d6142b6570fb4f59886 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Fri, 15 Jan 2016 22:20:21 +0100 Subject: [PATCH 02/14] geniuspaste: Rewrite to use configurable pastebins Rewrite the paste logic to support configurable pastebin services. Previously supported pastebin services are ported to the new format. This may break user configuration as it will reset the pastebin service in use to the new default. Closes #312. --- build/geniuspaste.m4 | 1 + geniuspaste/Makefile.am | 2 +- geniuspaste/README | 106 +++- geniuspaste/data/Makefile.am | 10 + geniuspaste/data/codepad.org.conf | 34 ++ geniuspaste/data/dpaste.de.conf | 64 +++ geniuspaste/data/pastebin.geany.org.conf | 35 ++ geniuspaste/data/sprunge.us.conf | 88 +++ geniuspaste/data/tinypaste.com.conf | 20 + geniuspaste/src/Makefile.am | 2 + geniuspaste/src/geniuspaste.c | 696 ++++++++++++++++------- 11 files changed, 836 insertions(+), 222 deletions(-) create mode 100644 geniuspaste/data/Makefile.am create mode 100644 geniuspaste/data/codepad.org.conf create mode 100644 geniuspaste/data/dpaste.de.conf create mode 100644 geniuspaste/data/pastebin.geany.org.conf create mode 100644 geniuspaste/data/sprunge.us.conf create mode 100644 geniuspaste/data/tinypaste.com.conf diff --git a/build/geniuspaste.m4 b/build/geniuspaste.m4 index d08ebfc54..dce1ab538 100644 --- a/build/geniuspaste.m4 +++ b/build/geniuspaste.m4 @@ -9,6 +9,7 @@ AC_DEFUN([GP_CHECK_GENIUSPASTE], AC_CONFIG_FILES([ geniuspaste/Makefile + geniuspaste/data/Makefile geniuspaste/src/Makefile ]) ]) diff --git a/geniuspaste/Makefile.am b/geniuspaste/Makefile.am index ba28f68dd..166b75f41 100644 --- a/geniuspaste/Makefile.am +++ b/geniuspaste/Makefile.am @@ -1,4 +1,4 @@ include $(top_srcdir)/build/vars.auxfiles.mk -SUBDIRS = src +SUBDIRS = data src plugin = geniuspaste diff --git a/geniuspaste/README b/geniuspaste/README index 513fb924f..ce3b593da 100644 --- a/geniuspaste/README +++ b/geniuspaste/README @@ -5,8 +5,9 @@ GeniusPaste Plugin About ----- -This plugin allows the user to paste the code from Geany into five different -pastebins. At the moment it supports this services: +This plugin allows the user to paste code from Geany into a configured +pastebin service. At the moment it ships with builtin support these pastebin +services, but more can be added: * codepad.org * tinypaste.com @@ -14,19 +15,20 @@ pastebins. At the moment it supports this services: * dpaste.de * sprunge.us -GeniusPaste detects automatically the syntax of the code and paste it +GeniusPaste detects automatically the syntax of the code and pastes it with syntax highlighting enabled. It can also display the pasted code opening a new browser tab. Issues ------ -The API of the pastebin services can be updated in every moment. It +The API of the pastebin services can be updated at every moment. It may happen that GeniusPaste plugin could use an outdated API that meanwhile has been deprecated. If you get a unexpected API response during the paste process (practically -if the plugin doesn't return a link to the pasted code) write a email to me -and warn me about the changes: +if the plugin doesn't return a link to the pasted code), you will need to fix +that pastebin service configuration to use its new API. If it is a +configuration shipped with the plugin, please report the issue to: @@ -34,12 +36,102 @@ Requirements ------------ * GTK+ >= 2.12 * libsoup 2.4 >= 2.4.0 - + Installation ------------ This plugin is part of the geany-plugins project. See the README file of that package. +Pastebin configuration +---------------------- + +Configuration for the pastebin services is looked up in one of data directories +as follows, in order: ``$GEANY_CONFIG_DIR/plugins/geniuspaste/pastebins/`` and +``$PREFIX/share/geany-plugins/geniuspaste/pastebins/`` (where +``$GEANY_CONFIG_DIR`` is ``$HOME/.config/geany`` on \*NIX; and ``$PREFIX`` is +generally either ``/usr/`` or ``/usr/local/`` under \*NIX). + +If more than one configuration file declare a pastebin service of the same +name, only the first one found is used. This way, one can easily override the +configuration in a system directory with a custom one in a user directory. + +Format +^^^^^^ + +The pastebin configuration format uses an INI-style syntax. + +Placeholders +++++++++++++ + +Values from the `[format] section`_ and the *replace* key in the `[parse] +section`_ can contain references to placeholders with the syntax ``%name%`` +(i.e. ``%contents%``). +Custom placeholders can be defined in the `[defaults] section`_. + +The builtin placeholders are: + +*contents* + The data to actually paste. +*language* + The language of the current document, as mapped through the `[languages] + section`_. +*title* + The base name of the current document. +*user* + The configured author name. + +*[pastebin]* section +++++++++++++++++++++ + +The *pastebin* section is required, and must contain at least the *name* and +*url* keys. + +*name* + The name of the pastebin service, as displayed to the user. This key is + required. +*url* + The URL to which submit the data. This key is required. +*method* + The HTTP method to use to submit the data. Defaults to ``POST``. + +*[format]* section +++++++++++++++++++ + +The *format* section describes the fields of the form to send to the pastebin. +Each key in this section is a field, and each value that field's value. + +*[parse]* section ++++++++++++++++++ + +The *parse* section defines the regular expression used to parse the raw +response from the pastebin service and build the final paste URL. + +*search* + A regular expression (PCRE) pattern to match against the pastebin + service's raw response data. + Defaults to ``^[[:space:]]*(.+?)[[:space:]]*$``, e.g. capture everything + but the leading and trailing whitespaces. +*replace* + The final URL, with regular expression capture groups from *search* + expanded. Group references use the numeric, one-digit syntax: ``\0`` + refers to the whole matched text, ``\1`` to the first captured group, and + so on. Defaults to ``\1``, which works nicely with the default *search* + pattern. + +*[defaults]* section +++++++++++++++++++++ + +The *defaults* section defines default values for some builtin `placeholders`_ +or define new placeholders. + +*[languages]* section ++++++++++++++++++++++ + +The *languages* section maps Geany's filetype names (the keys) to the pastebin +service's own name for this language. This allows to translate the filetypes +Geany knows about to ones the pastebin service understands. + + License ------- GeniusPaste is distributed under the terms of the GNU General Public diff --git a/geniuspaste/data/Makefile.am b/geniuspaste/data/Makefile.am new file mode 100644 index 000000000..dc29f0940 --- /dev/null +++ b/geniuspaste/data/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/build/vars.docs.mk +plugin = geniuspaste + +pastebinsdir = $(plugindatadir)/pastebins +dist_pastebins_DATA = \ + codepad.org.conf \ + dpaste.de.conf \ + pastebin.geany.org.conf \ + sprunge.us.conf \ + tinypaste.com.conf diff --git a/geniuspaste/data/codepad.org.conf b/geniuspaste/data/codepad.org.conf new file mode 100644 index 000000000..1eddfd332 --- /dev/null +++ b/geniuspaste/data/codepad.org.conf @@ -0,0 +1,34 @@ +[pastebin] +name=codepad.org +url=http://codepad.org/ + +[defaults] +language=Plain Text + +[format] +lang=%language% +code=%contents% +submit=Submit +#private=False +#run=True + +[parse] +search=\1 +replace=\1 + +# map GeanyFileType=PastebinFileType +[languages] +# as of 2016-01-15 +None=Plain Text +C=C +C++=C++ +D=D +Haskell=Haskell +Lua=Lua +CAML=OCaml +PHP=PHP +Perl=Perl +Python=Python +Ruby=Ruby +Scheme=Scheme +Tcl=Tcl diff --git a/geniuspaste/data/dpaste.de.conf b/geniuspaste/data/dpaste.de.conf new file mode 100644 index 000000000..af5effb18 --- /dev/null +++ b/geniuspaste/data/dpaste.de.conf @@ -0,0 +1,64 @@ +[pastebin] +name=dpaste.de +url=http://dpaste.de/api/ + +[format] +title=%title% +content=%contents% +lexer=%language% +#expires=%expire% + +[parse] +search=^.(.+).$ +replace=\1 + +[defaults] +# default language to plain which means "Code" +language=plain +expire=604800 + +# map GeanyFileType=PastebinFileType +[languages] +# list as of 2016-01-14 +None=text +ActionScript=as +C=c +# better than nothing +C++=c +#CAML=ocaml +Clojure=clojure +COBOL=cobol +Conf=ini +CSS=css +CUDA=cuda +Diff=diff +Docbook=xml +Erlang=erlang +F77=fortran +Fortran=fortran +GLSL=C +Go=go +Haskell=haskell +HTML=html +Java=java +Javascript=js +JSON=json +LaTeX=tex +Lua=lua +Make=make +Matlab/Octave=matlab +Objective-C=objc +Perl=perl +PHP=php +PowerShell=powershell +Python=python +reStructuredText=rst +Ruby=rb +Rust=rust +Scala=scala +Sh=bash +SQL=sql +Tcl=tcl +XML=xml +YAML=yaml +Zephir=php diff --git a/geniuspaste/data/pastebin.geany.org.conf b/geniuspaste/data/pastebin.geany.org.conf new file mode 100644 index 000000000..d2c7abf22 --- /dev/null +++ b/geniuspaste/data/pastebin.geany.org.conf @@ -0,0 +1,35 @@ +[pastebin] +name=pastebin.geany.org +url=http://pastebin.geany.org/api/ + +[format] +content=%contents% +author=%user% +title=%title% +lexer=%language% +#expire_options=%expire% + +[parse] +search=^.+$ +replace=\0 + +[defaults] +language=text +expire=2592000 + +# map GeanyFileType=PastebinFileType +[languages] +# list as of 2016-01-14 +C=c +# better than nothing +C++=c +CSS=css +Diff=diff +HTML=html +Javascript=js +Perl=perl +PHP=php +Python=python +reStructuredText=rst +SQL=sql +None=text diff --git a/geniuspaste/data/sprunge.us.conf b/geniuspaste/data/sprunge.us.conf new file mode 100644 index 000000000..0ef60c4bf --- /dev/null +++ b/geniuspaste/data/sprunge.us.conf @@ -0,0 +1,88 @@ +[pastebin] +name=sprunge.us +url=http://sprunge.us/ + +[format] +sprunge=%contents% + +[parse] +search=^[[:space:]]*(.+?)[[:space:]]*$ +replace=\1?%language% + +# map GeanyFileType=PastebinFileType +[languages] +# Pygments supports quite a bunch of stuff and is pretty forgiving, so +# basically just map 1:1 (but for a few things that either can't be used in +# and HTML query string (like C#), or for which Geany doesn't use the most +# common name (i.e. Conf)). +# Additionally, Sprunge seems to require lowercase names, but is happy with +# any lexer name, and just falls back to plain text if it can't find it, so we +# can safely just list everything and hope it works or will in the future. +None=text +Abaqus=abaqus +Abc=abc +ActionScript=actionscript +Ada=ada +Asciidoc=asciidoc +ASM=asm +Batch=batch +C=c +C#=csharp +C++=c++ +CAML=caml +Clojure=clojure +CMake=cmake +COBOL=cobol +CoffeeScript=coffeescript +Conf=conf +CSS=css +CUDA=cuda +Cython=cython +D=d +Diff=diff +Docbook=docbook +Erlang=erlang +F77=fortran +Ferite=ferite +Forth=forth +Fortran=fortran +FreeBasic=freebasic +Genie=genie +GLSL=glsl +Go=go +Graphviz=graphviz +Haskell=haskell +Haxe=haxe +HTML=html +Java=java +Javascript=javascript +JSON=json +LaTeX=latex +Lisp=lisp +Lua=lua +Make=make +Markdown=markdown +Matlab/Octave=octave +NSIS=nsis +Objective-C=objective-c +Pascal=pascal +Perl=perl +PHP=php +Po=po +PowerShell=powershell +Python=python +R=r +reStructuredText=restructuredtext +Ruby=ruby +Rust=rust +Scala=scala +Sh=sh +SQL=sql +Tcl=tcl +Txt2tags=txt2tags +Vala=vala +Verilog=verilog +VHDL=vhdl +XML=xml +YAML=yaml +Zephir=zephir diff --git a/geniuspaste/data/tinypaste.com.conf b/geniuspaste/data/tinypaste.com.conf new file mode 100644 index 000000000..ed709ff98 --- /dev/null +++ b/geniuspaste/data/tinypaste.com.conf @@ -0,0 +1,20 @@ +# FIXME: outdated, this leads to a 404 on http://pasted.co/ +[pastebin] +name=tinypaste.com +url=http://tinypaste.com/api/create.xml + +[format] +paste=%contents% +title=%title% +is_code=%language% + +[parse] +search=(.*) +replace=\1 + +[defaults] +language=1 + +# map GeanyFileType=PastebinFileType +[languages] +None=0 diff --git a/geniuspaste/src/Makefile.am b/geniuspaste/src/Makefile.am index f28694c82..84b79bdf1 100644 --- a/geniuspaste/src/Makefile.am +++ b/geniuspaste/src/Makefile.am @@ -1,4 +1,5 @@ include $(top_srcdir)/build/vars.build.mk +include $(top_srcdir)/build/vars.docs.mk plugin = geniuspaste geanyplugins_LTLIBRARIES = geniuspaste.la @@ -6,6 +7,7 @@ geanyplugins_LTLIBRARIES = geniuspaste.la geniuspaste_la_SOURCES = geniuspaste.c geniuspaste_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ + -DPLUGINDATADIR=\"$(plugindatadir)\" \ -DGEANY_VERSION=\"$(GEANY_VERSION)\" \ -DG_LOG_DOMAIN=\"GeniusPaste\" geniuspaste_la_CFLAGS = \ diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 732d340d6..7f0e38b8a 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -35,7 +35,7 @@ #define PLUGIN_NAME "GeniusPaste" -#define PLUGIN_VERSION "0.2" +#define PLUGIN_VERSION "0.3" #ifdef G_OS_WIN32 #define USERNAME g_getenv("USERNAME") @@ -50,14 +50,6 @@ #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX #endif -#define CODEPAD_ORG 0 -#define TINYPASTE_COM 1 -#define PASTEBIN_GEANY_ORG 2 -#define DPASTE_DE 3 -#define SPRUNGE_US 4 - -#define DEFAULT_TYPE_CODEPAD langs_supported_codepad[8]; -#define DEFAULT_TYPE_DPASTE langs_supported_dpaste[15]; GeanyPlugin *geany_plugin; GeanyData *geany_data; @@ -65,23 +57,14 @@ GeanyFunctions *geany_functions; static GtkWidget *main_menu_item = NULL; -static const gchar *websites[] = -{ - "codepad.org", - "tinypaste.com", - "pastebin.geany.org", - "dpaste.de", - "sprunge.us", -}; - -static const gchar *websites_api[] = +typedef struct { - "http://codepad.org/", - "http://tinypaste.com/api/create.xml", - "http://pastebin.geany.org/api/", - "http://dpaste.de/api/", - "http://sprunge.us/", -}; + gchar *name; + GKeyFile *config; +} +Pastebin; + +GSList *pastebins = NULL; static struct { @@ -93,7 +76,7 @@ static struct static gchar *config_file = NULL; static gchar *author_name = NULL; -static gint website_selected; +static gchar *pastebin_selected = NULL; static gboolean check_button_is_checked = FALSE; PLUGIN_VERSION_CHECK(147) @@ -101,10 +84,167 @@ PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE, PLUGIN_NAME, _("Paste your code on your favorite pastebin"), PLUGIN_VERSION, "Enrico Trotta ") -static gint indexof(const gchar * string, gchar c) + +static void pastebin_free(Pastebin *pastebin) +{ + g_key_file_unref(pastebin->config); + g_free(pastebin->name); + g_free(pastebin); +} + +/* like g_key_file_has_group() but sets error if the group is missing */ +static gboolean ensure_keyfile_has_group(GKeyFile *kf, + const gchar *group, + GError **error) +{ + if (g_key_file_has_group(kf, group)) + return TRUE; + else + { + g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND, + _("Group \"%s\" not found."), group); + return FALSE; + } +} + +/* like g_key_file_has_key() but sets error if either the group or the key is + * missing */ +static gboolean ensure_keyfile_has_key(GKeyFile *kf, + const gchar *group, + const gchar *key, + GError **error) { - gchar * occ = strchr(string, c); - return occ ? occ - string : -1; + if (! ensure_keyfile_has_group(kf, group, error)) + return FALSE; + else if (g_key_file_has_key(kf, group, key, NULL)) + return TRUE; + else + { + g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND, + _("Group \"%s\" has no key \"%s\"."), group, key); + return FALSE; + } +} + +static Pastebin *pastebin_new(const gchar *path, + GError **error) +{ + Pastebin *pastebin = NULL; + GKeyFile *kf = g_key_file_new(); + + if (g_key_file_load_from_file(kf, path, 0, error) && + ensure_keyfile_has_key(kf, "pastebin", "name", error) && + ensure_keyfile_has_key(kf, "pastebin", "url", error) && + ensure_keyfile_has_group(kf, "format", error)) + { + pastebin = g_malloc(sizeof *pastebin); + + pastebin->name = g_key_file_get_string(kf, "pastebin", "name", NULL); + pastebin->config = g_key_file_ref(kf); + } + + g_key_file_unref(kf); + + return pastebin; +} + +static const Pastebin *find_pastebin_by_name(const gchar *name) +{ + GSList *node; + + g_return_val_if_fail(name != NULL, NULL); + + for (node = pastebins; node; node = node->next) + { + Pastebin *pastebin = node->data; + + if (strcmp(pastebin->name, name) == 0) + return pastebin; + } + + return NULL; +} + +static gint sort_pastebins(gconstpointer a, gconstpointer b) +{ + return utils_str_casecmp(((Pastebin *) a)->name, ((Pastebin *) b)->name); +} + +static void load_pastebins_in_dir(const gchar *path) +{ + GError *err = NULL; + GDir *dir = g_dir_open(path, 0, &err); + + if (! dir && err->code != G_FILE_ERROR_NOENT) + g_critical("Failed to read directory %s: %s", path, err->message); + if (err) + g_clear_error(&err); + if (dir) + { + const gchar *filename; + + while ((filename = g_dir_read_name(dir))) + { + if (*filename == '.') /* silently skip dotfiles */ + continue; + else if (! g_str_has_suffix(filename, ".conf")) + { + g_debug("Skipping %s%s%s because it has no .conf extension", + path, G_DIR_SEPARATOR_S, filename); + continue; + } + else + { + gchar *fpath = g_build_filename(path, filename, NULL); + Pastebin *pastebin = pastebin_new(fpath, &err); + + if (! pastebin) + { + g_critical("Invalid pastebin configuration file %s: %s", + fpath, err->message); + g_clear_error(&err); + } + else + { + if (! find_pastebin_by_name(pastebin->name)) + pastebins = g_slist_prepend(pastebins, pastebin); + else + { + g_debug("Skipping duplicate configuration \"%s\" for " + "pastebin \"%s\"", fpath, pastebin->name); + pastebin_free(pastebin); + } + } + + g_free(fpath); + } + } + + g_dir_close(dir); + } +} + +static void load_all_pastebins(void) +{ + gchar *paths[] = { + g_build_filename(geany->app->configdir, "plugins", "geniuspaste", + "pastebins", NULL), + g_build_filename(PLUGINDATADIR, "pastebins", NULL) + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(paths); i++) + { + load_pastebins_in_dir(paths[i]); + g_free(paths[i]); + } + pastebins = g_slist_sort(pastebins, sort_pastebins); +} + +static void free_all_pastebins(void) +{ + g_slist_free_full(pastebins, (GDestroyNotify) pastebin_free); + pastebins = NULL; } static void load_settings(void) @@ -116,11 +256,11 @@ static void load_settings(void) config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S, "geniuspaste", G_DIR_SEPARATOR_S, "geniuspaste.conf", NULL); g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); - - website_selected = utils_get_setting_integer(config, "geniuspaste", "website", PASTEBIN_GEANY_ORG); + + pastebin_selected = utils_get_setting_string(config, "geniuspaste", "website", "pastebin.geany.org"); check_button_is_checked = utils_get_setting_boolean(config, "geniuspaste", "open_browser", FALSE); author_name = utils_get_setting_string(config, "geniuspaste", "author_name", USERNAME); - + g_key_file_free(config); } @@ -132,7 +272,7 @@ static void save_settings(void) g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); - g_key_file_set_integer(config, "geniuspaste", "website", website_selected); + g_key_file_set_string(config, "geniuspaste", "website", pastebin_selected); g_key_file_set_boolean(config, "geniuspaste", "open_browser", check_button_is_checked); g_key_file_set_string(config, "geniuspaste", "author_name", author_name); @@ -147,7 +287,7 @@ static void save_settings(void) utils_write_file(config_file, data); g_free(data); } - + g_free(config_dir); g_key_file_free(config); } @@ -174,239 +314,359 @@ static gchar *get_paste_text(GeanyDocument *doc, gsize *text_len) return paste_text; } -static void paste(GeanyDocument * doc, const gchar * website) +static gchar *pastebin_get_default(const Pastebin *pastebin, + const gchar *key, + const gchar *def) { - SoupSession *session; - SoupMessage *msg = NULL; + return utils_get_setting_string(pastebin->config, "defaults", key, def); +} - gchar *f_content; - gchar const *f_type; - gchar *f_title; - gchar *p_url; - gchar *formdata = NULL; - gchar *user_agent = NULL; - gchar **tokens_array; +static gchar *pastebin_get_language(const Pastebin *pastebin, + const gchar *geany_ft_name) +{ + gchar *lang = g_key_file_get_string(pastebin->config, "languages", + geany_ft_name, NULL); - const gchar *langs_supported_codepad[] = - { - "C", "C++", "D", "Haskell", - "Lua", "OCaml", "PHP", "Perl", "Plain Text", - "Python", "Ruby", "Scheme", "Tcl" - }; + return lang ? lang : pastebin_get_default(pastebin, "language", ""); +} - const gchar *langs_supported_dpaste[] = +/* append replacement for placeholder @placeholder to @str + * returns %TRUE on success, %FALSE if the placeholder didn't exist + * + * don't prepare replacements because they are unlikely to happen more than + * once for each paste */ +static gboolean append_placeholder(GString *str, + const gchar *placeholder, + /* some replacement sources */ + const Pastebin *pastebin, + GeanyDocument *doc, + const gchar *contents) +{ + /* special builtin placeholders */ + if (strcmp("contents", placeholder) == 0) + g_string_append(str, contents); + else if (strcmp("language", placeholder) == 0) { - "Bash", "C", "CSS", "Diff", - "Django/Jinja", "HTML", "IRC logs", "JavaScript", "PHP", - "Python console session", "Python Traceback", "Python", - "Python3", "Restructured Text", "SQL", "Text only" - }; - - gint occ_position; - guint i; - guint status; - gsize f_length; + gchar *language = pastebin_get_language(pastebin, doc->file_type->name); - g_return_if_fail(doc && doc->is_valid); + g_string_append(str, language); + g_free(language); + } + else if (strcmp("title", placeholder) == 0) + { + gchar *title; - f_type = doc->file_type->name; + if (doc->file_name) + title = g_path_get_basename(doc->file_name); + else + title = document_get_basename_for_display(doc, -1); - if (doc->file_name == NULL) - f_title = document_get_basename_for_display(doc, -1); + g_string_append(str, title); + g_free(title); + } + else if (strcmp("user", placeholder) == 0) + g_string_append(str, author_name); + /* non-builtins (ones from [defaults] alone) */ else - f_title = g_path_get_basename(doc->file_name); - - load_settings(); - - f_content = get_paste_text(doc, &f_length); - if (f_content == NULL || f_content[0] == '\0') { - dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Refusing to create blank paste")); - return; + gchar *val = pastebin_get_default(pastebin, placeholder, NULL); + + if (val) + { + g_string_append(str, val); + g_free(val); + } + else + { + g_warning("non-existing placeholder \"%%%s%%\"", placeholder); + return FALSE; + } } - switch (website_selected) + return TRUE; +} + +/* expands "%name%" placeholders in @string + * + * placeholders are of the form: + * "%" [a-zA-Z0-9_]+ "%" + * Only valid and supported placeholders are replaced; everything else is left + * as-is. */ +static gchar *expand_placeholders(const gchar *string, + const Pastebin *pastebin, + GeanyDocument *doc, + const gchar *contents) +{ + GString *str = g_string_new(NULL); + const gchar *p; + + for (; (p = strchr(string, '%')); string = p + 1) { + const gchar *k = p + 1; + gchar *key = NULL; + gsize key_len = 0; - case CODEPAD_ORG: + g_string_append_len(str, string, p - string); - for (i = 0; i < G_N_ELEMENTS(langs_supported_codepad); i++) - { - if (g_strcmp0(f_type, langs_supported_codepad[i]) == 0) - break; - else - f_type = DEFAULT_TYPE_CODEPAD; - } + while (g_ascii_isalnum(k[key_len]) || k[key_len] == '_') + key_len++; + + if (key_len > 0 && k[key_len] == '%') + key = g_strndup(k, key_len); - msg = soup_message_new("POST", website); - formdata = soup_form_encode("lang", f_type, - "code", f_content, - "submit", "Submit", - NULL); + if (! key) + g_string_append_len(str, p, k + key_len - p); + else if (! append_placeholder(str, key, pastebin, doc, contents)) + g_string_append_len(str, p, k + key_len + 1 - p); - break; + if (key) /* skip % suffix too */ + key_len++; - case TINYPASTE_COM: + g_free(key); - msg = soup_message_new("POST", website); - formdata = soup_form_encode("paste", f_content, - "title", f_title, - "is_code", g_strcmp0(f_type, "None") == 0 ? "0" : "1", - NULL); + p = k + key_len - 1; + } + g_string_append(str, string); - break; + return g_string_free(str, FALSE); +} +/* Matches @pattern on @haystack and perform match substitutions in @replace */ +static gchar *regex_replace(const gchar *pattern, + const gchar *haystack, + const gchar *replace, + GError **error) +{ + GRegex *re = g_regex_new(pattern, (G_REGEX_DOTALL | + G_REGEX_DOLLAR_ENDONLY | + G_REGEX_RAW), 0, error); + GMatchInfo *info = NULL; + gchar *result = NULL; - case DPASTE_DE: + if (re && g_regex_match(re, haystack, 0, &info)) + { + GString *str = g_string_new(NULL); + const gchar *p; - for (i = 0; i < G_N_ELEMENTS(langs_supported_dpaste); i++) + for (; (p = strchr(replace, '\\')); replace = p + 1) { - if (g_strcmp0(f_type, langs_supported_dpaste[i]) == 0) - break; + gint digit = ((gint) p[1]) - '0'; + + g_string_append_len(str, replace, p - replace); + if (digit >= 0 && digit <= 9 && + digit < g_match_info_get_match_count(info)) + { + gchar *match = g_match_info_fetch(info, digit); + + g_string_append(str, match); + g_free(match); + p++; + } else - f_type = DEFAULT_TYPE_DPASTE; + g_string_append_c(str, *p); } + g_string_append(str, replace); - msg = soup_message_new("POST", website); - /* apparently dpaste.de detects automatically the syntax of the - * pasted code so 'lexer' should be unneeded - */ - formdata = soup_form_encode("content", f_content, - "title", f_title, - "lexer", f_type, - NULL); + result = g_string_free(str, FALSE); + } - break; + if (info) + g_match_info_free(info); - case SPRUNGE_US: + return result; +} - msg = soup_message_new("POST", website); - formdata = soup_form_encode("sprunge", f_content, NULL); +static void free_data_item(GQuark id, gpointer data, gpointer user_data) +{ + g_free(data); +} - break; +/* sends data to @pastebin and returns the raw response */ +static SoupMessage *pastebin_soup_message_new(const Pastebin *pastebin, + GeanyDocument *doc, + const gchar *contents) +{ + SoupMessage *msg; + gchar *url; + gchar *method; + gsize n_fields; + gchar **fields; + GData *data; + + g_return_val_if_fail(pastebin != NULL, NULL); + g_return_val_if_fail(contents != NULL, NULL); + + url = utils_get_setting_string(pastebin->config, "pastebin", "url", NULL); + method = utils_get_setting_string(pastebin->config, "pastebin", "method", "POST"); + /* prepare the form data */ + fields = g_key_file_get_keys(pastebin->config, "format", &n_fields, NULL); + g_datalist_init(&data); + for (gsize i = 0; fields && i < n_fields; i++) + { + gchar *value = g_key_file_get_string(pastebin->config, "format", fields[i], NULL); - case PASTEBIN_GEANY_ORG: + SETPTR(value, expand_placeholders(value, pastebin, doc, contents)); + g_datalist_set_data(&data, fields[i], value); + } + g_strfreev(fields); + msg = soup_form_request_new_from_datalist(method, url, &data); + g_datalist_foreach(&data, free_data_item, NULL); + g_datalist_clear(&data); - msg = soup_message_new("POST", website); - formdata = soup_form_encode("content", f_content, - "author", author_name, - "title", f_title, - "lexer", f_type, - NULL); + return msg; +} - break; +/* parses @response and returns the URL of the paste, or %NULL on parse error + * or if the URL couldn't be found. + * @warning: it may return NULL even if @error is not set */ +static gchar *pastebin_parse_response(const Pastebin *pastebin, + const gchar *response, + GeanyDocument *doc, + const gchar *contents, + GError **error) +{ + gchar *url = NULL; + gchar *search; + gchar *replace; - } + g_return_val_if_fail(pastebin != NULL, NULL); + g_return_val_if_fail(response != NULL, NULL); - g_free(f_content); + search = utils_get_setting_string(pastebin->config, "parse", "search", + "^[[:space:]]*(.+?)[[:space:]]*$"); + replace = utils_get_setting_string(pastebin->config, "parse", "replace", "\\1"); + SETPTR(replace, expand_placeholders(replace, pastebin, doc, contents)); - user_agent = g_strconcat(PLUGIN_NAME, " ", PLUGIN_VERSION, " / Geany ", GEANY_VERSION, NULL); - session = soup_session_async_new_with_options(SOUP_SESSION_USER_AGENT, user_agent, NULL); - g_free(user_agent); + url = regex_replace(search, response, replace, error); - soup_message_set_request(msg, "application/x-www-form-urlencoded", - SOUP_MEMORY_TAKE, formdata, strlen(formdata)); + g_free(search); + g_free(replace); - status = soup_session_send_message(session, msg); - p_url = g_strdup(msg->response_body->data); + return url; +} - g_object_unref(session); - g_object_unref(msg); +G_GNUC_PRINTF (4, 5) +static void show_msgbox(GtkMessageType type, GtkButtonsType buttons, + const gchar *main_text, + const gchar *secondary_markup, ...) +{ + GtkWidget *dlg; + va_list ap; + gchar *markup; + + va_start(ap, secondary_markup); + markup = g_markup_vprintf_escaped(secondary_markup, ap); + va_end(ap); + + dlg = g_object_new(GTK_TYPE_MESSAGE_DIALOG, + "message-type", type, + "buttons", buttons, + "transient-for", geany->main_widgets->window, + "modal", TRUE, + "destroy-with-parent", TRUE, + "text", main_text, + "secondary-text", markup, + "secondary-use-markup", TRUE, + NULL); + gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_destroy(dlg); +} - if(status == SOUP_STATUS_OK) - { +static void paste(GeanyDocument * doc, const gchar * website) +{ + const Pastebin *pastebin; + gchar *f_content; + gsize f_length; + SoupSession *session; + SoupMessage *msg; + gchar *user_agent = NULL; + guint status; - /* - * codepad.org doesn't return only the url of the new snippet pasted - * but an html page. This minimal parser will get the bare url. - */ + g_return_if_fail(doc && doc->is_valid); - if (website_selected == CODEPAD_ORG) - { - tokens_array = g_strsplit(p_url, " - * - * xxxxx - * - */ - tokens_array = g_strsplit_set(p_url, "<>", 0); - - SETPTR(p_url, g_strdup_printf("http://%s/%s", websites[TINYPASTE_COM], tokens_array[6])); - - g_strfreev(tokens_array); - } - - else if(website_selected == DPASTE_DE) - { - SETPTR(p_url, g_strndup(p_url + 1, strlen(p_url) - 2)); + if (! SOUP_STATUS_IS_SUCCESSFUL(status)) + { + show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Failed to paste the code"), + _("Error pasting the code to the pastebin service %s.\n" + "Error code: %u (%s).\n\n%s"), + pastebin->name, status, msg->reason_phrase, + (SOUP_STATUS_IS_TRANSPORT_ERROR(status) + ? _("Check your connection or the pastebin configuration and retry.") + : SOUP_STATUS_IS_SERVER_ERROR(status) + ? _("Wait for the service to come back and retry, or retry " + "with another pastebin service.") + : _("Check the pastebin configuration and retry."))); + } + else + { + GError *err = NULL; + gchar *p_url = pastebin_parse_response(pastebin, msg->response_body->data, + doc, f_content, &err); - } - else if(website_selected == SPRUNGE_US) + if (err || ! p_url) { - - /* in order to enable the syntax highlightning on sprunge.us - * it is necessary to append at the returned url a question - * mark '?' followed by the file type. - * - * e.g. sprunge.us/xxxx?c - */ - gchar *ft_tmp = g_ascii_strdown(f_type, -1); - g_strstrip(p_url); - SETPTR(p_url, g_strdup_printf("%s?%s", p_url, ft_tmp)); - g_free(ft_tmp); + show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Failed to obtain paste URL."), + _("The code was successfully pasted on %s, but an " + "error occurred trying to obtain its URL: %s\n\n%s"), + pastebin->name, + (err ? err->message + : _("unexpected response from the pastebin service.")), + _("Check the pastebin configuration and retry.")); + + if (err) + g_error_free(err); } - - if (check_button_is_checked) + else if (check_button_is_checked) { utils_open_browser(p_url); } else { - GtkWidget *dlg = gtk_message_dialog_new(GTK_WINDOW(geany->main_widgets->window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, - _("Paste Successful")); - gtk_message_dialog_format_secondary_markup(GTK_MESSAGE_DIALOG(dlg), - _("Your paste can be found here:\n%s"), p_url, p_url); - gtk_dialog_run(GTK_DIALOG(dlg)); - gtk_widget_destroy(dlg); + show_msgbox(GTK_MESSAGE_INFO, GTK_BUTTONS_OK, + _("Paste Successful"), + _("Your paste can be found here:\n%s"), + p_url, p_url); } - } - else - { - dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Unable to paste the code. Check your connection and retry.\n" - "Error code: %d\n"), status); + + g_free(p_url); } - g_free(p_url); + g_object_unref(msg); + g_free(f_content); } static void item_activate(GtkMenuItem * menuitem, gpointer gdata) @@ -419,7 +679,7 @@ static void item_activate(GtkMenuItem * menuitem, gpointer gdata) return; } - paste(doc, websites_api[website_selected]); + paste(doc, pastebin_selected); } static void on_configure_response(GtkDialog * dialog, gint response, gpointer * user_data) @@ -432,7 +692,7 @@ static void on_configure_response(GtkDialog * dialog, gint response, gpointer * } else { - website_selected = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets.combo)); + SETPTR(pastebin_selected, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(widgets.combo))); check_button_is_checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.check_button)); SETPTR(author_name, g_strdup(gtk_entry_get_text(GTK_ENTRY(widgets.author_entry)))); save_settings(); @@ -442,7 +702,8 @@ static void on_configure_response(GtkDialog * dialog, gint response, gpointer * GtkWidget *plugin_configure(GtkDialog * dialog) { - guint i; + GSList *node; + gint i; GtkWidget *label, *vbox, *author_label; vbox = gtk_vbox_new(FALSE, 6); @@ -462,8 +723,14 @@ GtkWidget *plugin_configure(GtkDialog * dialog) widgets.combo = gtk_combo_box_text_new(); - for (i = 0; i < G_N_ELEMENTS(websites); i++) - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widgets.combo), websites[i]); + for (i = 0, node = pastebins; node; node = node->next, i++) + { + Pastebin *pastebin = node->data; + + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widgets.combo), pastebin->name); + if (pastebin_selected && strcmp(pastebin_selected, pastebin->name) == 0) + gtk_combo_box_set_active(GTK_COMBO_BOX(widgets.combo), i); + } widgets.check_button = gtk_check_button_new_with_label(_("Show your paste in a new browser tab")); @@ -473,7 +740,6 @@ GtkWidget *plugin_configure(GtkDialog * dialog) gtk_box_pack_start(GTK_BOX(vbox), widgets.author_entry, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), widgets.check_button, FALSE, FALSE, 0); - gtk_combo_box_set_active(GTK_COMBO_BOX(widgets.combo), website_selected); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.check_button), check_button_is_checked); gtk_widget_show_all(vbox); @@ -499,6 +765,7 @@ static void add_menu_item(void) void plugin_init(GeanyData * data) { + load_all_pastebins(); load_settings(); main_locale_init(LOCALEDIR, GETTEXT_PACKAGE); add_menu_item(); @@ -509,4 +776,5 @@ void plugin_cleanup(void) { g_free(author_name); gtk_widget_destroy(main_menu_item); + free_all_pastebins(); } From 323e6dbcb81889ee0a000c5a866ee2546ca77e76 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 20:14:31 +0100 Subject: [PATCH 03/14] geniuspaste: Restore configuration compatibility with previous version --- geniuspaste/src/geniuspaste.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 7f0e38b8a..940e20cb2 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -257,7 +257,24 @@ static void load_settings(void) "geniuspaste", G_DIR_SEPARATOR_S, "geniuspaste.conf", NULL); g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); - pastebin_selected = utils_get_setting_string(config, "geniuspaste", "website", "pastebin.geany.org"); + if (g_key_file_has_key(config, "geniuspaste", "pastebin", NULL) || + ! g_key_file_has_key(config, "geniuspaste", "website", NULL)) + { + pastebin_selected = utils_get_setting_string(config, "geniuspaste", "pastebin", "pastebin.geany.org"); + } + else + { + /* compatibility for old setting "website" */ + switch (utils_get_setting_integer(config, "geniuspaste", "website", 2)) + { + case 0: pastebin_selected = g_strdup("codepad.org"); break; + case 1: pastebin_selected = g_strdup("tinypaste.com"); break; + default: + case 2: pastebin_selected = g_strdup("pastebin.geany.org"); break; + case 3: pastebin_selected = g_strdup("dpaste.de"); break; + case 4: pastebin_selected = g_strdup("sprunge.us"); break; + } + } check_button_is_checked = utils_get_setting_boolean(config, "geniuspaste", "open_browser", FALSE); author_name = utils_get_setting_string(config, "geniuspaste", "author_name", USERNAME); @@ -272,7 +289,7 @@ static void save_settings(void) g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); - g_key_file_set_string(config, "geniuspaste", "website", pastebin_selected); + g_key_file_set_string(config, "geniuspaste", "pastebin", pastebin_selected); g_key_file_set_boolean(config, "geniuspaste", "open_browser", check_button_is_checked); g_key_file_set_string(config, "geniuspaste", "author_name", author_name); From 803fc391653126e37076d42fdfcffd8e4af01afc Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 20:15:14 +0100 Subject: [PATCH 04/14] geniuspaste: Simplify getting the paste title --- geniuspaste/src/geniuspaste.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 940e20cb2..c171e37e6 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -371,12 +371,7 @@ static gboolean append_placeholder(GString *str, } else if (strcmp("title", placeholder) == 0) { - gchar *title; - - if (doc->file_name) - title = g_path_get_basename(doc->file_name); - else - title = document_get_basename_for_display(doc, -1); + gchar *title = g_path_get_basename(DOC_FILENAME(doc)); g_string_append(str, title); g_free(title); From 59132c28ade43f50e2ff9bd2e3a74e6b42353964 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 20:26:01 +0100 Subject: [PATCH 05/14] geniuspaste: Use constants instead of direct string lookup Use constants instead of direct strings for pastebin service configuration lookup to lower the risk of typos. --- geniuspaste/src/geniuspaste.c | 43 +++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index c171e37e6..17ac2c2bc 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -50,6 +50,16 @@ #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX #endif +#define PASTEBIN_GROUP_DEFAULTS "defaults" +#define PASTEBIN_GROUP_FORMAT "format" +#define PASTEBIN_GROUP_LANGUAGES "languages" +#define PASTEBIN_GROUP_PARSE "parse" +#define PASTEBIN_GROUP_PARSE_KEY_SEARCH "search" +#define PASTEBIN_GROUP_PARSE_KEY_REPLACE "replace" +#define PASTEBIN_GROUP_PASTEBIN "pastebin" +#define PASTEBIN_GROUP_PASTEBIN_KEY_NAME "name" +#define PASTEBIN_GROUP_PASTEBIN_KEY_URL "url" +#define PASTEBIN_GROUP_PASTEBIN_KEY_METHOD "method" GeanyPlugin *geany_plugin; GeanyData *geany_data; @@ -133,13 +143,16 @@ static Pastebin *pastebin_new(const gchar *path, GKeyFile *kf = g_key_file_new(); if (g_key_file_load_from_file(kf, path, 0, error) && - ensure_keyfile_has_key(kf, "pastebin", "name", error) && - ensure_keyfile_has_key(kf, "pastebin", "url", error) && - ensure_keyfile_has_group(kf, "format", error)) + ensure_keyfile_has_key(kf, PASTEBIN_GROUP_PASTEBIN, + PASTEBIN_GROUP_PASTEBIN_KEY_NAME, error) && + ensure_keyfile_has_key(kf, PASTEBIN_GROUP_PASTEBIN, + PASTEBIN_GROUP_PASTEBIN_KEY_URL, error) && + ensure_keyfile_has_group(kf, PASTEBIN_GROUP_FORMAT, error)) { pastebin = g_malloc(sizeof *pastebin); - pastebin->name = g_key_file_get_string(kf, "pastebin", "name", NULL); + pastebin->name = g_key_file_get_string(kf, PASTEBIN_GROUP_PASTEBIN, + PASTEBIN_GROUP_PASTEBIN_KEY_NAME, NULL); pastebin->config = g_key_file_ref(kf); } @@ -335,13 +348,14 @@ static gchar *pastebin_get_default(const Pastebin *pastebin, const gchar *key, const gchar *def) { - return utils_get_setting_string(pastebin->config, "defaults", key, def); + return utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_DEFAULTS, + key, def); } static gchar *pastebin_get_language(const Pastebin *pastebin, const gchar *geany_ft_name) { - gchar *lang = g_key_file_get_string(pastebin->config, "languages", + gchar *lang = g_key_file_get_string(pastebin->config, PASTEBIN_GROUP_LANGUAGES, geany_ft_name, NULL); return lang ? lang : pastebin_get_default(pastebin, "language", ""); @@ -508,14 +522,17 @@ static SoupMessage *pastebin_soup_message_new(const Pastebin *pastebin, g_return_val_if_fail(pastebin != NULL, NULL); g_return_val_if_fail(contents != NULL, NULL); - url = utils_get_setting_string(pastebin->config, "pastebin", "url", NULL); - method = utils_get_setting_string(pastebin->config, "pastebin", "method", "POST"); + url = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PASTEBIN, + PASTEBIN_GROUP_PASTEBIN_KEY_URL, NULL); + method = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PASTEBIN, + PASTEBIN_GROUP_PASTEBIN_KEY_METHOD, "POST"); /* prepare the form data */ - fields = g_key_file_get_keys(pastebin->config, "format", &n_fields, NULL); + fields = g_key_file_get_keys(pastebin->config, PASTEBIN_GROUP_FORMAT, &n_fields, NULL); g_datalist_init(&data); for (gsize i = 0; fields && i < n_fields; i++) { - gchar *value = g_key_file_get_string(pastebin->config, "format", fields[i], NULL); + gchar *value = g_key_file_get_string(pastebin->config, PASTEBIN_GROUP_FORMAT, + fields[i], NULL); SETPTR(value, expand_placeholders(value, pastebin, doc, contents)); g_datalist_set_data(&data, fields[i], value); @@ -544,9 +561,11 @@ static gchar *pastebin_parse_response(const Pastebin *pastebin, g_return_val_if_fail(pastebin != NULL, NULL); g_return_val_if_fail(response != NULL, NULL); - search = utils_get_setting_string(pastebin->config, "parse", "search", + search = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, + PASTEBIN_GROUP_PARSE_KEY_SEARCH, "^[[:space:]]*(.+?)[[:space:]]*$"); - replace = utils_get_setting_string(pastebin->config, "parse", "replace", "\\1"); + replace = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, + PASTEBIN_GROUP_PARSE_KEY_REPLACE, "\\1"); SETPTR(replace, expand_placeholders(replace, pastebin, doc, contents)); url = regex_replace(search, response, replace, error); From 8f97f3856d838322bb20474b5f5250d6f563c68e Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 22:43:13 +0100 Subject: [PATCH 06/14] geniuspaste: Show request and response in debug mode This eases debugging a faulty pastebin configuration by showing the actual data sent and received. --- geniuspaste/src/geniuspaste.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 17ac2c2bc..e7992c78b 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -646,6 +646,24 @@ static void paste(GeanyDocument * doc, const gchar * website) status = soup_session_send_message(session, msg); g_object_unref(session); + if (geany->app->debug_mode) + { + gchar *real_uri = soup_uri_to_string(soup_message_get_uri(msg), FALSE); + + soup_message_body_flatten(msg->request_body); + msgwin_msg_add(COLOR_BLUE, -1, NULL, + "[geniuspaste] %s\n" + "Request: %s\n" + "Response: %s\n" + "Code: %d (%s)", + real_uri, + msg->request_body->data, + msg->response_body->data, + msg->status_code, + msg->reason_phrase); + g_free(real_uri); + } + if (! SOUP_STATUS_IS_SUCCESSFUL(status)) { show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, From a7bf3a73b85aa8b136aa6905c2ec0905d111f76f Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 22:46:08 +0100 Subject: [PATCH 07/14] geniuspaste: Add fpaste.org support --- geniuspaste/data/Makefile.am | 1 + geniuspaste/data/fpaste.org.conf | 67 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 geniuspaste/data/fpaste.org.conf diff --git a/geniuspaste/data/Makefile.am b/geniuspaste/data/Makefile.am index dc29f0940..f5ade62eb 100644 --- a/geniuspaste/data/Makefile.am +++ b/geniuspaste/data/Makefile.am @@ -5,6 +5,7 @@ pastebinsdir = $(plugindatadir)/pastebins dist_pastebins_DATA = \ codepad.org.conf \ dpaste.de.conf \ + fpaste.org.conf \ pastebin.geany.org.conf \ sprunge.us.conf \ tinypaste.com.conf diff --git a/geniuspaste/data/fpaste.org.conf b/geniuspaste/data/fpaste.org.conf new file mode 100644 index 000000000..bcb510df4 --- /dev/null +++ b/geniuspaste/data/fpaste.org.conf @@ -0,0 +1,67 @@ +[pastebin] +name=fpaste.org +url=http://fpaste.org/ + +[format] +paste_data=%contents% +paste_lang=%language% +api_submit=true +mode=xml + +# Optional stuff + +paste_user=%author% +# expiration in seconds +#paste_expire=0 + +[parse] +search=(.+?) +replace=http://fpaste.org/\1 + +# map GeanyFileType=PastebinFileType +[languages] +# map for GeSHi 2015-01-14 +ActionScript=Actionscript +Ada=ADA +ASM=ASM +C=C +C#=C# +C++=C++ +COBOL=COBOL +Conf=INI +CSS=CSS +CUDA=C +Cython=Python +D=D +Diff=Diff +Docbook=XML +F77=Fortran +Fortran=Fortran +FreeBasic=FreeBasic +GLSL=C +Haskell=Haskell +HTML=HTML +Java=Java +Javascript=Javascript +JSON=Javascript +LaTeX=LaTeX +Lisp=Lisp +Lua=Lua +Make=Make +NSIS=NSIS +Objective-C=Objective-C +Pascal=Pascal +Perl=Perl +PHP=PHP +Po=GetText +PowerShell=PowerShell +Prolog=Prolog +Python=Python +Ruby=Ruby +Scala=Scala +Sh=Bash +SQL=SQL +Tcl=TCL +Verilog=Verilog +VHDL=VHDL +XML=XML From e052cc3718dd638a443f67f9a18c41396ca37f49 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sat, 16 Jan 2016 22:50:05 +0100 Subject: [PATCH 08/14] geniuspaste: Remove unused code --- geniuspaste/src/geniuspaste.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index e7992c78b..96149e3ba 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -322,25 +322,20 @@ static void save_settings(void) g_key_file_free(config); } -static gchar *get_paste_text(GeanyDocument *doc, gsize *text_len) +static gchar *get_paste_text(GeanyDocument *doc) { - gsize len; gchar *paste_text; if (sci_has_selection(doc->editor->sci)) { - len = sci_get_selected_text_length(doc->editor->sci) + 1; paste_text = sci_get_selection_contents(doc->editor->sci); } else { - len = sci_get_length(doc->editor->sci) + 1; + gint len = sci_get_length(doc->editor->sci) + 1; paste_text = sci_get_contents(doc->editor->sci, len); } - if (text_len) - *text_len = len; - return paste_text; } @@ -607,7 +602,6 @@ static void paste(GeanyDocument * doc, const gchar * website) { const Pastebin *pastebin; gchar *f_content; - gsize f_length; SoupSession *session; SoupMessage *msg; gchar *user_agent = NULL; @@ -630,7 +624,7 @@ static void paste(GeanyDocument * doc, const gchar * website) } /* get the contents */ - f_content = get_paste_text(doc, &f_length); + f_content = get_paste_text(doc); if (f_content == NULL || f_content[0] == '\0') { dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Refusing to create blank paste")); From 649939a4490034e51ff69e02d5380f8b7664d882 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:19:57 +0100 Subject: [PATCH 09/14] geniuspaste: Add support for using the redirected URI as paste URI Most non-API-based pastebin services will redirect the client to the newly created paste page. It is then safer to use the redirect URI than trying to parse some possibly complex HTML to extract the URI. --- geniuspaste/README | 14 ++++++++++++-- geniuspaste/src/geniuspaste.c | 34 +++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/geniuspaste/README b/geniuspaste/README index ce3b593da..d7fdff5d3 100644 --- a/geniuspaste/README +++ b/geniuspaste/README @@ -103,8 +103,18 @@ Each key in this section is a field, and each value that field's value. *[parse]* section +++++++++++++++++ -The *parse* section defines the regular expression used to parse the raw -response from the pastebin service and build the final paste URL. +If the *parse* section is declared, it defines the regular expression used to +parse the raw response body from the pastebin service and build the final +paste URL. + +If this section doesn't exist, the URI of the response itself is used, after +any possible redirection. This is usually good for non-API pastebin services, +where the server redirects the user to the paste page. + +**Warning:** If the *parse* section exists, it will be used, no matter whether +any key is actually defined. This means that a ``[parse]`` line is enough to +enable response body parsing, and it will use the default *search* and +*replace* settings. *search* A regular expression (PCRE) pattern to match against the pastebin diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 96149e3ba..02f9f8e19 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -544,7 +544,7 @@ static SoupMessage *pastebin_soup_message_new(const Pastebin *pastebin, * or if the URL couldn't be found. * @warning: it may return NULL even if @error is not set */ static gchar *pastebin_parse_response(const Pastebin *pastebin, - const gchar *response, + SoupMessage *msg, GeanyDocument *doc, const gchar *contents, GError **error) @@ -554,19 +554,27 @@ static gchar *pastebin_parse_response(const Pastebin *pastebin, gchar *replace; g_return_val_if_fail(pastebin != NULL, NULL); - g_return_val_if_fail(response != NULL, NULL); + g_return_val_if_fail(msg != NULL, NULL); - search = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, - PASTEBIN_GROUP_PARSE_KEY_SEARCH, - "^[[:space:]]*(.+?)[[:space:]]*$"); - replace = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, - PASTEBIN_GROUP_PARSE_KEY_REPLACE, "\\1"); - SETPTR(replace, expand_placeholders(replace, pastebin, doc, contents)); + if (! g_key_file_has_group(pastebin->config, PASTEBIN_GROUP_PARSE)) + { + /* by default, use the response URI (redirect) */ + url = soup_uri_to_string(soup_message_get_uri(msg), FALSE); + } + else + { + search = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, + PASTEBIN_GROUP_PARSE_KEY_SEARCH, + "^[[:space:]]*(.+?)[[:space:]]*$"); + replace = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE, + PASTEBIN_GROUP_PARSE_KEY_REPLACE, "\\1"); + SETPTR(replace, expand_placeholders(replace, pastebin, doc, contents)); - url = regex_replace(search, response, replace, error); + url = regex_replace(search, msg->response_body->data, replace, error); - g_free(search); - g_free(replace); + g_free(search); + g_free(replace); + } return url; } @@ -675,8 +683,8 @@ static void paste(GeanyDocument * doc, const gchar * website) else { GError *err = NULL; - gchar *p_url = pastebin_parse_response(pastebin, msg->response_body->data, - doc, f_content, &err); + gchar *p_url = pastebin_parse_response(pastebin, msg, doc, f_content, + &err); if (err || ! p_url) { From b801b5685d09392c4454e83c0303bde538e643ea Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:25:19 +0100 Subject: [PATCH 10/14] geniuspaste: Port CodePad configuration to use server redirect --- geniuspaste/data/codepad.org.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/geniuspaste/data/codepad.org.conf b/geniuspaste/data/codepad.org.conf index 1eddfd332..af95ab7f8 100644 --- a/geniuspaste/data/codepad.org.conf +++ b/geniuspaste/data/codepad.org.conf @@ -12,10 +12,6 @@ submit=Submit #private=False #run=True -[parse] -search=\1 -replace=\1 - # map GeanyFileType=PastebinFileType [languages] # as of 2016-01-15 From 39b746b4410631647351e8d4e69ab4827a8ac73b Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:31:41 +0100 Subject: [PATCH 11/14] geniuspaste: Fix fpaste.org user --- geniuspaste/data/fpaste.org.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geniuspaste/data/fpaste.org.conf b/geniuspaste/data/fpaste.org.conf index bcb510df4..68b966ced 100644 --- a/geniuspaste/data/fpaste.org.conf +++ b/geniuspaste/data/fpaste.org.conf @@ -10,7 +10,7 @@ mode=xml # Optional stuff -paste_user=%author% +paste_user=%user% # expiration in seconds #paste_expire=0 From 2c6f963a7d063e41a9da308be502bbe681ca1847 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:32:34 +0100 Subject: [PATCH 12/14] geniuspaste: Consolidate dpaste.de response parsing --- geniuspaste/data/dpaste.de.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geniuspaste/data/dpaste.de.conf b/geniuspaste/data/dpaste.de.conf index af5effb18..0f64d2142 100644 --- a/geniuspaste/data/dpaste.de.conf +++ b/geniuspaste/data/dpaste.de.conf @@ -9,7 +9,7 @@ lexer=%language% #expires=%expire% [parse] -search=^.(.+).$ +search=^"(.+)"$ replace=\1 [defaults] From 5d4dc34ccb08a065117c7ab9fe4eed8f54fce340 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:33:13 +0100 Subject: [PATCH 13/14] geniuspaste: Log both request and response separately for debugging The request and response might be sensibly different, and their respective URI especially matters, so log them both separately. --- geniuspaste/src/geniuspaste.c | 43 +++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/geniuspaste/src/geniuspaste.c b/geniuspaste/src/geniuspaste.c index 02f9f8e19..cd45d83b7 100644 --- a/geniuspaste/src/geniuspaste.c +++ b/geniuspaste/src/geniuspaste.c @@ -606,6 +606,29 @@ static void show_msgbox(GtkMessageType type, GtkButtonsType buttons, gtk_widget_destroy(dlg); } +static void debug_log_message_body(SoupMessage *msg, + SoupMessageBody *body, + const gchar *label) +{ + if (geany->app->debug_mode) + { + gchar *real_uri = soup_uri_to_string(soup_message_get_uri(msg), FALSE); + + soup_message_body_flatten(body); + msgwin_msg_add(COLOR_BLUE, -1, NULL, + "[geniuspaste] %s:\n" + "URI: %s\n" + "Body: %s\n" + "Code: %d (%s)", + label, + real_uri, + body->data, + msg->status_code, + msg->reason_phrase); + g_free(real_uri); + } +} + static void paste(GeanyDocument * doc, const gchar * website) { const Pastebin *pastebin; @@ -645,26 +668,12 @@ static void paste(GeanyDocument * doc, const gchar * website) session = soup_session_async_new_with_options(SOUP_SESSION_USER_AGENT, user_agent, NULL); g_free(user_agent); + debug_log_message_body(msg, msg->request_body, "Request"); + status = soup_session_send_message(session, msg); g_object_unref(session); - if (geany->app->debug_mode) - { - gchar *real_uri = soup_uri_to_string(soup_message_get_uri(msg), FALSE); - - soup_message_body_flatten(msg->request_body); - msgwin_msg_add(COLOR_BLUE, -1, NULL, - "[geniuspaste] %s\n" - "Request: %s\n" - "Response: %s\n" - "Code: %d (%s)", - real_uri, - msg->request_body->data, - msg->response_body->data, - msg->status_code, - msg->reason_phrase); - g_free(real_uri); - } + debug_log_message_body(msg, msg->response_body, "Response"); if (! SOUP_STATUS_IS_SUCCESSFUL(status)) { From a459a192f62ad21b02f353fb6a7e8d2e5111b94c Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 17 Jan 2016 00:44:41 +0100 Subject: [PATCH 14/14] geniuspaste: Add paste.debian.net pastebin service --- geniuspaste/data/Makefile.am | 1 + geniuspaste/data/paste.debian.net.conf | 67 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 geniuspaste/data/paste.debian.net.conf diff --git a/geniuspaste/data/Makefile.am b/geniuspaste/data/Makefile.am index f5ade62eb..61086ebae 100644 --- a/geniuspaste/data/Makefile.am +++ b/geniuspaste/data/Makefile.am @@ -7,5 +7,6 @@ dist_pastebins_DATA = \ dpaste.de.conf \ fpaste.org.conf \ pastebin.geany.org.conf \ + paste.debian.net.conf \ sprunge.us.conf \ tinypaste.com.conf diff --git a/geniuspaste/data/paste.debian.net.conf b/geniuspaste/data/paste.debian.net.conf new file mode 100644 index 000000000..a6d39a70c --- /dev/null +++ b/geniuspaste/data/paste.debian.net.conf @@ -0,0 +1,67 @@ +[pastebin] +name=paste.debian.net +url=http://paste.debian.net/ + +[format] +code=%contents% +poster=%user% +lang=%language% +# expire is required +expire=%expire% +#private=0 +#wrap=0 + +[defaults] +language=-1 +expire=604800 + +# map GeanyFileType=PastebinFileType +[languages] +# list as of 2016-01-16 +Ada=ada +ActionScript=as +Batch=bat +C=c +C++=cpp +C#=csharp +CAML=ocaml +Clojure=clojure +CMake=cmake +CoffeeScript=coffee-script +Conf=ini +CSS=css +Cython=cython +D=d +Diff=diff +Docbook=XML +Erlang=erlang +F77=fortran +Fortran=fortran +GLSL=glsl +Go=go +Haskell=haskell +HTML=html+php +Java=java +Javascript=js +JSON=json +LaTeX=tex +Lua=lua +Lisp=common-lisp +Make=make +Matlab/Octave=octave +Objective-C=objective-c +Perl=perl +PHP=html+php +Po=pot +PowerShell=powershell +Python=python +reStructuredText=rst +Ruby=rb +Scala=scala +Sh=bash +SQL=sql +Tcl=tcl +Vala=vala +VHDL=vhdl +XML=xml +YAML=yaml