1 /*
2 * geniuspaste - paste your code on your favorite pastebin.
3 *
4 * Copyright 2012 Enrico "Enrix835" Trotta <enrico.trt@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #include <libsoup/soup.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #ifdef HAVE_LOCALE_H
27 #include <locale.h>
28 #endif
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h" /* for the gettext domain */
32 #endif
33
34 #include <geanyplugin.h>
35
36
37 #define PLUGIN_NAME "GeniusPaste"
38 #define PLUGIN_VERSION "0.3"
39
40 #ifdef G_OS_WIN32
41 #define USERNAME g_getenv("USERNAME")
42 #else
43 #define USERNAME g_getenv("USER")
44 #endif
45
46 /* stay compatible with GTK < 2.24 */
47 #if !GTK_CHECK_VERSION(2,24,0)
48 #define gtk_combo_box_text_new gtk_combo_box_new_text
49 #define gtk_combo_box_text_append_text gtk_combo_box_append_text
50 #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX
51 #endif
52
53 #define PASTEBIN_GROUP_DEFAULTS "defaults"
54 #define PASTEBIN_GROUP_FORMAT "format"
55 #define PASTEBIN_GROUP_LANGUAGES "languages"
56 #define PASTEBIN_GROUP_PARSE "parse"
57 #define PASTEBIN_GROUP_PARSE_KEY_SEARCH "search"
58 #define PASTEBIN_GROUP_PARSE_KEY_REPLACE "replace"
59 #define PASTEBIN_GROUP_PASTEBIN "pastebin"
60 #define PASTEBIN_GROUP_PASTEBIN_KEY_NAME "name"
61 #define PASTEBIN_GROUP_PASTEBIN_KEY_URL "url"
62 #define PASTEBIN_GROUP_PASTEBIN_KEY_METHOD "method"
63 #define PASTEBIN_GROUP_PASTEBIN_KEY_CONTENT_TYPE "content-type"
64
65 GeanyPlugin *geany_plugin;
66 GeanyData *geany_data;
67
68 static GtkWidget *main_menu_item = NULL;
69
70 typedef struct
71 {
72 gchar *name;
73 GKeyFile *config;
74 }
75 Pastebin;
76
77 typedef enum
78 {
79 FORMAT_HTML_FORM_URLENCODED,
80 FORMAT_JSON
81 }
82 Format;
83
84 GSList *pastebins = NULL;
85
86 static struct
87 {
88 GtkWidget *combo;
89 GtkWidget *check_button;
90 GtkWidget *author_entry;
91 } widgets;
92
93 static gchar *config_file = NULL;
94 static gchar *author_name = NULL;
95
96 static gchar *pastebin_selected = NULL;
97 static gboolean check_button_is_checked = FALSE;
98
99 PLUGIN_VERSION_CHECK(224)
100 PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE, PLUGIN_NAME,
101 _("Paste your code on your favorite pastebin"),
102 PLUGIN_VERSION, "Enrico Trotta <enrico.trt@gmail.com>")
103
104
pastebin_free(Pastebin * pastebin)105 static void pastebin_free(Pastebin *pastebin)
106 {
107 g_key_file_free(pastebin->config);
108 g_free(pastebin->name);
109 g_free(pastebin);
110 }
111
112 /* like g_key_file_has_group() but sets error if the group is missing */
ensure_keyfile_has_group(GKeyFile * kf,const gchar * group,GError ** error)113 static gboolean ensure_keyfile_has_group(GKeyFile *kf,
114 const gchar *group,
115 GError **error)
116 {
117 if (g_key_file_has_group(kf, group))
118 return TRUE;
119 else
120 {
121 g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
122 _("Group \"%s\" not found."), group);
123 return FALSE;
124 }
125 }
126
127 /* like g_key_file_has_key() but sets error if either the group or the key is
128 * missing */
ensure_keyfile_has_key(GKeyFile * kf,const gchar * group,const gchar * key,GError ** error)129 static gboolean ensure_keyfile_has_key(GKeyFile *kf,
130 const gchar *group,
131 const gchar *key,
132 GError **error)
133 {
134 if (! ensure_keyfile_has_group(kf, group, error))
135 return FALSE;
136 else if (g_key_file_has_key(kf, group, key, NULL))
137 return TRUE;
138 else
139 {
140 g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
141 _("Group \"%s\" has no key \"%s\"."), group, key);
142 return FALSE;
143 }
144 }
145
pastebin_new(const gchar * path,GError ** error)146 static Pastebin *pastebin_new(const gchar *path,
147 GError **error)
148 {
149 Pastebin *pastebin = NULL;
150 GKeyFile *kf = g_key_file_new();
151
152 if (g_key_file_load_from_file(kf, path, 0, error) &&
153 ensure_keyfile_has_key(kf, PASTEBIN_GROUP_PASTEBIN,
154 PASTEBIN_GROUP_PASTEBIN_KEY_NAME, error) &&
155 ensure_keyfile_has_key(kf, PASTEBIN_GROUP_PASTEBIN,
156 PASTEBIN_GROUP_PASTEBIN_KEY_URL, error) &&
157 ensure_keyfile_has_group(kf, PASTEBIN_GROUP_FORMAT, error))
158 {
159 pastebin = g_malloc(sizeof *pastebin);
160
161 pastebin->name = g_key_file_get_string(kf, PASTEBIN_GROUP_PASTEBIN,
162 PASTEBIN_GROUP_PASTEBIN_KEY_NAME, NULL);
163 pastebin->config = kf;
164 }
165 else
166 g_key_file_free(kf);
167
168 return pastebin;
169 }
170
find_pastebin_by_name(const gchar * name)171 static const Pastebin *find_pastebin_by_name(const gchar *name)
172 {
173 GSList *node;
174
175 g_return_val_if_fail(name != NULL, NULL);
176
177 for (node = pastebins; node; node = node->next)
178 {
179 Pastebin *pastebin = node->data;
180
181 if (strcmp(pastebin->name, name) == 0)
182 return pastebin;
183 }
184
185 return NULL;
186 }
187
sort_pastebins(gconstpointer a,gconstpointer b)188 static gint sort_pastebins(gconstpointer a, gconstpointer b)
189 {
190 return utils_str_casecmp(((Pastebin *) a)->name, ((Pastebin *) b)->name);
191 }
192
load_pastebins_in_dir(const gchar * path)193 static void load_pastebins_in_dir(const gchar *path)
194 {
195 GError *err = NULL;
196 GDir *dir = g_dir_open(path, 0, &err);
197
198 if (! dir && err->code != G_FILE_ERROR_NOENT)
199 g_critical("Failed to read directory %s: %s", path, err->message);
200 if (err)
201 g_clear_error(&err);
202 if (dir)
203 {
204 const gchar *filename;
205
206 while ((filename = g_dir_read_name(dir)))
207 {
208 if (*filename == '.') /* silently skip dotfiles */
209 continue;
210 else if (! g_str_has_suffix(filename, ".conf"))
211 {
212 g_debug("Skipping %s%s%s because it has no .conf extension",
213 path, G_DIR_SEPARATOR_S, filename);
214 continue;
215 }
216 else
217 {
218 gchar *fpath = g_build_filename(path, filename, NULL);
219 Pastebin *pastebin = pastebin_new(fpath, &err);
220
221 if (! pastebin)
222 {
223 g_critical("Invalid pastebin configuration file %s: %s",
224 fpath, err->message);
225 g_clear_error(&err);
226 }
227 else
228 {
229 if (! find_pastebin_by_name(pastebin->name))
230 pastebins = g_slist_prepend(pastebins, pastebin);
231 else
232 {
233 g_debug("Skipping duplicate configuration \"%s\" for "
234 "pastebin \"%s\"", fpath, pastebin->name);
235 pastebin_free(pastebin);
236 }
237 }
238
239 g_free(fpath);
240 }
241 }
242
243 g_dir_close(dir);
244 }
245 }
246
get_data_dir_path(const gchar * filename)247 static gchar *get_data_dir_path(const gchar *filename)
248 {
249 gchar *prefix = NULL;
250 gchar *path;
251
252 #ifdef G_OS_WIN32
253 prefix = g_win32_get_package_installation_directory_of_module(NULL);
254 #elif defined(__APPLE__)
255 if (g_getenv("GEANY_PLUGINS_SHARE_PATH"))
256 return g_build_filename(g_getenv("GEANY_PLUGINS_SHARE_PATH"),
257 PLUGIN, filename, NULL);
258 #endif
259 path = g_build_filename(prefix ? prefix : "", PLUGINDATADIR, filename, NULL);
260 g_free(prefix);
261 return path;
262 }
263
load_all_pastebins(void)264 static void load_all_pastebins(void)
265 {
266 gchar *paths[] = {
267 g_build_filename(geany->app->configdir, "plugins", "geniuspaste",
268 "pastebins", NULL),
269 get_data_dir_path("pastebins")
270 };
271 guint i;
272
273 for (i = 0; i < G_N_ELEMENTS(paths); i++)
274 {
275 load_pastebins_in_dir(paths[i]);
276 g_free(paths[i]);
277 }
278 pastebins = g_slist_sort(pastebins, sort_pastebins);
279 }
280
free_all_pastebins(void)281 static void free_all_pastebins(void)
282 {
283 g_slist_free_full(pastebins, (GDestroyNotify) pastebin_free);
284 pastebins = NULL;
285 }
286
load_settings(void)287 static void load_settings(void)
288 {
289 GKeyFile *config = g_key_file_new();
290
291 if (config_file)
292 g_free(config_file);
293 config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S,
294 "geniuspaste", G_DIR_SEPARATOR_S, "geniuspaste.conf", NULL);
295 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
296
297 if (g_key_file_has_key(config, "geniuspaste", "pastebin", NULL) ||
298 ! g_key_file_has_key(config, "geniuspaste", "website", NULL))
299 {
300 pastebin_selected = utils_get_setting_string(config, "geniuspaste", "pastebin", "pastebin.geany.org");
301 }
302 else
303 {
304 /* compatibility for old setting "website" */
305 switch (utils_get_setting_integer(config, "geniuspaste", "website", 2))
306 {
307 case 0: pastebin_selected = g_strdup("codepad.org"); break;
308 case 1: pastebin_selected = g_strdup("tinypaste.com"); break;
309 default:
310 case 2: pastebin_selected = g_strdup("pastebin.geany.org"); break;
311 case 3: pastebin_selected = g_strdup("dpaste.de"); break;
312 case 4: pastebin_selected = g_strdup("sprunge.us"); break;
313 }
314 }
315 check_button_is_checked = utils_get_setting_boolean(config, "geniuspaste", "open_browser", FALSE);
316 author_name = utils_get_setting_string(config, "geniuspaste", "author_name", USERNAME);
317
318 g_key_file_free(config);
319 }
320
save_settings(void)321 static void save_settings(void)
322 {
323 GKeyFile *config = g_key_file_new();
324 gchar *data;
325 gchar *config_dir = g_path_get_dirname(config_file);
326
327 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
328
329 g_key_file_set_string(config, "geniuspaste", "pastebin", pastebin_selected);
330 g_key_file_set_boolean(config, "geniuspaste", "open_browser", check_button_is_checked);
331 g_key_file_set_string(config, "geniuspaste", "author_name", author_name);
332
333 if (! g_file_test(config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir(config_dir, TRUE) != 0)
334 {
335 dialogs_show_msgbox(GTK_MESSAGE_ERROR,
336 _("Plugin configuration directory could not be created."));
337 }
338 else
339 {
340 data = g_key_file_to_data(config, NULL, NULL);
341 utils_write_file(config_file, data);
342 g_free(data);
343 }
344
345 g_free(config_dir);
346 g_key_file_free(config);
347 }
348
get_paste_text(GeanyDocument * doc)349 static gchar *get_paste_text(GeanyDocument *doc)
350 {
351 gchar *paste_text;
352
353 if (sci_has_selection(doc->editor->sci))
354 {
355 paste_text = sci_get_selection_contents(doc->editor->sci);
356 }
357 else
358 {
359 gint len = sci_get_length(doc->editor->sci) + 1;
360 paste_text = sci_get_contents(doc->editor->sci, len);
361 }
362
363 return paste_text;
364 }
365
pastebin_get_default(const Pastebin * pastebin,const gchar * key,const gchar * def)366 static gchar *pastebin_get_default(const Pastebin *pastebin,
367 const gchar *key,
368 const gchar *def)
369 {
370 return utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_DEFAULTS,
371 key, def);
372 }
373
pastebin_get_language(const Pastebin * pastebin,const gchar * geany_ft_name)374 static gchar *pastebin_get_language(const Pastebin *pastebin,
375 const gchar *geany_ft_name)
376 {
377 gchar *lang = g_key_file_get_string(pastebin->config, PASTEBIN_GROUP_LANGUAGES,
378 geany_ft_name, NULL);
379
380 return lang ? lang : pastebin_get_default(pastebin, "language", "");
381 }
382
383 /* append replacement for placeholder @placeholder to @str
384 * returns %TRUE on success, %FALSE if the placeholder didn't exist
385 *
386 * don't prepare replacements because they are unlikely to happen more than
387 * once for each paste */
append_placeholder(GString * str,const gchar * placeholder,const Pastebin * pastebin,GeanyDocument * doc,const gchar * contents)388 static gboolean append_placeholder(GString *str,
389 const gchar *placeholder,
390 /* some replacement sources */
391 const Pastebin *pastebin,
392 GeanyDocument *doc,
393 const gchar *contents)
394 {
395 /* special builtin placeholders */
396 if (strcmp("contents", placeholder) == 0)
397 g_string_append(str, contents);
398 else if (strcmp("language", placeholder) == 0)
399 {
400 gchar *language = pastebin_get_language(pastebin, doc->file_type->name);
401
402 g_string_append(str, language);
403 g_free(language);
404 }
405 else if (strcmp("title", placeholder) == 0)
406 {
407 gchar *title = g_path_get_basename(DOC_FILENAME(doc));
408
409 g_string_append(str, title);
410 g_free(title);
411 }
412 else if (strcmp("user", placeholder) == 0)
413 g_string_append(str, author_name);
414 /* non-builtins (ones from [defaults] alone) */
415 else
416 {
417 gchar *val = pastebin_get_default(pastebin, placeholder, NULL);
418
419 if (val)
420 {
421 g_string_append(str, val);
422 g_free(val);
423 }
424 else
425 {
426 g_warning("non-existing placeholder \"%%%s%%\"", placeholder);
427 return FALSE;
428 }
429 }
430
431 return TRUE;
432 }
433
434 /* expands "%name%" placeholders in @string
435 *
436 * placeholders are of the form:
437 * "%" [a-zA-Z0-9_]+ "%"
438 * Only valid and supported placeholders are replaced; everything else is left
439 * as-is. */
expand_placeholders(const gchar * string,const Pastebin * pastebin,GeanyDocument * doc,const gchar * contents)440 static gchar *expand_placeholders(const gchar *string,
441 const Pastebin *pastebin,
442 GeanyDocument *doc,
443 const gchar *contents)
444 {
445 GString *str = g_string_new(NULL);
446 const gchar *p;
447
448 for (; (p = strchr(string, '%')); string = p + 1)
449 {
450 const gchar *k = p + 1;
451 gchar *key = NULL;
452 gsize key_len = 0;
453
454 g_string_append_len(str, string, p - string);
455
456 while (g_ascii_isalnum(k[key_len]) || k[key_len] == '_')
457 key_len++;
458
459 if (key_len > 0 && k[key_len] == '%')
460 key = g_strndup(k, key_len);
461
462 if (! key)
463 g_string_append_len(str, p, k + key_len - p);
464 else if (! append_placeholder(str, key, pastebin, doc, contents))
465 g_string_append_len(str, p, k + key_len + 1 - p);
466
467 if (key) /* skip % suffix too */
468 key_len++;
469
470 g_free(key);
471
472 p = k + key_len - 1;
473 }
474 g_string_append(str, string);
475
476 return g_string_free(str, FALSE);
477 }
478
479 /* Matches @pattern on @haystack and perform match substitutions in @replace */
regex_replace(const gchar * pattern,const gchar * haystack,const gchar * replace,GError ** error)480 static gchar *regex_replace(const gchar *pattern,
481 const gchar *haystack,
482 const gchar *replace,
483 GError **error)
484 {
485 GRegex *re = g_regex_new(pattern, (G_REGEX_DOTALL |
486 G_REGEX_DOLLAR_ENDONLY |
487 G_REGEX_RAW), 0, error);
488 GMatchInfo *info = NULL;
489 gchar *result = NULL;
490
491 if (re && g_regex_match(re, haystack, 0, &info))
492 {
493 GString *str = g_string_new(NULL);
494 const gchar *p;
495
496 for (; (p = strchr(replace, '\\')); replace = p + 1)
497 {
498 gint digit = ((gint) p[1]) - '0';
499
500 g_string_append_len(str, replace, p - replace);
501 if (digit >= 0 && digit <= 9 &&
502 digit < g_match_info_get_match_count(info))
503 {
504 gchar *match = g_match_info_fetch(info, digit);
505
506 g_string_append(str, match);
507 g_free(match);
508 p++;
509 }
510 else
511 g_string_append_c(str, *p);
512 }
513 g_string_append(str, replace);
514
515 result = g_string_free(str, FALSE);
516 }
517
518 if (info)
519 g_match_info_free(info);
520
521 return result;
522 }
523
pastebin_get_format(const Pastebin * pastebin)524 static Format pastebin_get_format(const Pastebin *pastebin)
525 {
526 static const struct
527 {
528 const gchar *name;
529 Format format;
530 } formats[] = {
531 { "application/x-www-form-urlencoded", FORMAT_HTML_FORM_URLENCODED },
532 { "application/json", FORMAT_JSON }
533 };
534 Format result = FORMAT_HTML_FORM_URLENCODED;
535 gchar *format = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PASTEBIN,
536 PASTEBIN_GROUP_PASTEBIN_KEY_CONTENT_TYPE, NULL);
537
538 if (format)
539 {
540 for (guint i = 0; i < G_N_ELEMENTS(formats); i++)
541 {
542 if (strcmp(formats[i].name, format) == 0)
543 {
544 result = formats[i].format;
545 break;
546 }
547 }
548
549 g_free(format);
550 }
551
552 return result;
553 }
554
555 /* Appends a JSON string. See:
556 * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf */
append_json_string(GString * str,const gchar * value)557 static void append_json_string(GString *str, const gchar *value)
558 {
559 g_string_append_c(str, '"');
560 for (; *value; value++)
561 {
562 if (*value == '"' || *value == '\\')
563 {
564 g_string_append_c(str, '\\');
565 g_string_append_c(str, *value);
566 }
567 else if (*value == '\b')
568 g_string_append(str, "\\b");
569 else if (*value == '\f')
570 g_string_append(str, "\\f");
571 else if (*value == '\n')
572 g_string_append(str, "\\n");
573 else if (*value == '\r')
574 g_string_append(str, "\\r");
575 else if (*value == '\t')
576 g_string_append(str, "\\t");
577 else if (*value >= 0x00 && *value <= 0x1F)
578 g_string_append_printf(str, "\\u%04d", *value);
579 else
580 g_string_append_c(str, *value);
581 }
582 g_string_append_c(str, '"');
583 }
584
append_json_data_item(GQuark id,gpointer data,gpointer user_data)585 static void append_json_data_item(GQuark id, gpointer data, gpointer user_data)
586 {
587 GString *str = user_data;
588
589 if (str->len > 1) /* if there's more than the first "{" */
590 g_string_append_c(str, ',');
591 append_json_string(str, g_quark_to_string(id));
592 g_string_append_c(str, ':');
593 append_json_string(str, data);
594 }
595
json_request_new(const gchar * method,const gchar * url,GData ** fields)596 static SoupMessage *json_request_new(const gchar *method,
597 const gchar *url,
598 GData **fields)
599 {
600 SoupMessage *msg = soup_message_new(method, url);
601 GString *str = g_string_new(NULL);
602
603 g_string_append_c(str, '{');
604 g_datalist_foreach(fields, append_json_data_item, str);
605 g_string_append_c(str, '}');
606 soup_message_set_request(msg, "application/json", SOUP_MEMORY_TAKE,
607 str->str, str->len);
608 g_string_free(str, FALSE);
609
610 return msg;
611 }
612
613 /* sends data to @pastebin and returns the raw response */
pastebin_soup_message_new(const Pastebin * pastebin,GeanyDocument * doc,const gchar * contents)614 static SoupMessage *pastebin_soup_message_new(const Pastebin *pastebin,
615 GeanyDocument *doc,
616 const gchar *contents)
617 {
618 SoupMessage *msg;
619 gchar *url;
620 gchar *method;
621 Format format;
622 gsize n_fields;
623 gchar **fields;
624 GData *data;
625
626 g_return_val_if_fail(pastebin != NULL, NULL);
627 g_return_val_if_fail(contents != NULL, NULL);
628
629 url = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PASTEBIN,
630 PASTEBIN_GROUP_PASTEBIN_KEY_URL, NULL);
631 method = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PASTEBIN,
632 PASTEBIN_GROUP_PASTEBIN_KEY_METHOD, "POST");
633 format = pastebin_get_format(pastebin);
634 /* prepare the form data */
635 fields = g_key_file_get_keys(pastebin->config, PASTEBIN_GROUP_FORMAT, &n_fields, NULL);
636 g_datalist_init(&data);
637 for (gsize i = 0; fields && i < n_fields; i++)
638 {
639 gchar *value = g_key_file_get_string(pastebin->config, PASTEBIN_GROUP_FORMAT,
640 fields[i], NULL);
641
642 SETPTR(value, expand_placeholders(value, pastebin, doc, contents));
643 g_datalist_set_data_full(&data, fields[i], value, g_free);
644 }
645 g_strfreev(fields);
646 switch (format)
647 {
648 case FORMAT_JSON:
649 msg = json_request_new(method, url, &data);
650 break;
651
652 default:
653 case FORMAT_HTML_FORM_URLENCODED:
654 msg = soup_form_request_new_from_datalist(method, url, &data);
655 break;
656 }
657 g_datalist_clear(&data);
658
659 return msg;
660 }
661
662 /* parses @response and returns the URL of the paste, or %NULL on parse error
663 * or if the URL couldn't be found.
664 * @warning: it may return NULL even if @error is not set */
pastebin_parse_response(const Pastebin * pastebin,SoupMessage * msg,GeanyDocument * doc,const gchar * contents,GError ** error)665 static gchar *pastebin_parse_response(const Pastebin *pastebin,
666 SoupMessage *msg,
667 GeanyDocument *doc,
668 const gchar *contents,
669 GError **error)
670 {
671 gchar *url = NULL;
672 gchar *search;
673 gchar *replace;
674
675 g_return_val_if_fail(pastebin != NULL, NULL);
676 g_return_val_if_fail(msg != NULL, NULL);
677
678 if (! g_key_file_has_group(pastebin->config, PASTEBIN_GROUP_PARSE))
679 {
680 /* by default, use the response URI (redirect) */
681 url = soup_uri_to_string(soup_message_get_uri(msg), FALSE);
682 }
683 else
684 {
685 search = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE,
686 PASTEBIN_GROUP_PARSE_KEY_SEARCH,
687 "^[[:space:]]*(.+?)[[:space:]]*$");
688 replace = utils_get_setting_string(pastebin->config, PASTEBIN_GROUP_PARSE,
689 PASTEBIN_GROUP_PARSE_KEY_REPLACE, "\\1");
690 SETPTR(replace, expand_placeholders(replace, pastebin, doc, contents));
691
692 url = regex_replace(search, msg->response_body->data, replace, error);
693
694 g_free(search);
695 g_free(replace);
696 }
697
698 return url;
699 }
700
message_dialog_label_link_activated(GtkLabel * label,gchar * uri,gpointer user_data)701 static gboolean message_dialog_label_link_activated(GtkLabel *label, gchar *uri, gpointer user_data)
702 {
703 utils_open_browser(uri);
704 return TRUE;
705 }
706
message_dialog_label_set_url_hook(GtkWidget * widget,gpointer data)707 static void message_dialog_label_set_url_hook(GtkWidget *widget, gpointer data)
708 {
709 if (GTK_IS_LABEL(widget))
710 {
711 g_signal_connect(widget,
712 "activate-link",
713 G_CALLBACK(message_dialog_label_link_activated),
714 NULL);
715 }
716 }
717
718 G_GNUC_PRINTF (4, 5)
show_msgbox(GtkMessageType type,GtkButtonsType buttons,const gchar * main_text,const gchar * secondary_markup,...)719 static void show_msgbox(GtkMessageType type, GtkButtonsType buttons,
720 const gchar *main_text,
721 const gchar *secondary_markup, ...)
722 {
723 GtkWidget *dlg;
724 GtkWidget *dlg_vbox;
725 va_list ap;
726 gchar *markup;
727
728 va_start(ap, secondary_markup);
729 markup = g_markup_vprintf_escaped(secondary_markup, ap);
730 va_end(ap);
731
732 dlg = g_object_new(GTK_TYPE_MESSAGE_DIALOG,
733 "message-type", type,
734 "buttons", buttons,
735 "transient-for", geany->main_widgets->window,
736 "modal", TRUE,
737 "destroy-with-parent", TRUE,
738 "text", main_text,
739 "secondary-text", markup,
740 "secondary-use-markup", TRUE,
741 NULL);
742 /* fetch the message area of the dialog and attach a custom URL hook to the labels */
743 dlg_vbox = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dlg));
744 gtk_container_foreach(GTK_CONTAINER(dlg_vbox),
745 message_dialog_label_set_url_hook,
746 NULL);
747 /* run the dialog */
748 gtk_dialog_run(GTK_DIALOG(dlg));
749 gtk_widget_destroy(dlg);
750 }
751
debug_log_message_body(SoupMessage * msg,SoupMessageBody * body,const gchar * label)752 static void debug_log_message_body(SoupMessage *msg,
753 SoupMessageBody *body,
754 const gchar *label)
755 {
756 if (geany->app->debug_mode)
757 {
758 gchar *real_uri = soup_uri_to_string(soup_message_get_uri(msg), FALSE);
759
760 soup_message_body_flatten(body);
761 msgwin_msg_add(COLOR_BLUE, -1, NULL,
762 "[geniuspaste] %s:\n"
763 "URI: %s\n"
764 "Body: %s\n"
765 "Code: %d (%s)",
766 label,
767 real_uri,
768 body->data,
769 msg->status_code,
770 msg->reason_phrase);
771 g_free(real_uri);
772 }
773 }
774
paste(GeanyDocument * doc,const gchar * website)775 static void paste(GeanyDocument * doc, const gchar * website)
776 {
777 const Pastebin *pastebin;
778 gchar *f_content;
779 SoupSession *session;
780 SoupMessage *msg;
781 gchar *user_agent = NULL;
782 guint status;
783
784 g_return_if_fail(doc && doc->is_valid);
785
786 /* find the pastebin */
787 pastebin = find_pastebin_by_name(website);
788 if (! pastebin)
789 {
790 show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
791 _("Invalid pastebin service."),
792 _("Unknown pastebin service \"%s\". "
793 "Select an existing pastebin service in the preferences "
794 "or create an appropriate pastebin configuration and "
795 "retry."),
796 website);
797 return;
798 }
799
800 /* get the contents */
801 f_content = get_paste_text(doc);
802 if (f_content == NULL || f_content[0] == '\0')
803 {
804 dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Refusing to create blank paste"));
805 return;
806 }
807
808 msg = pastebin_soup_message_new(pastebin, doc, f_content);
809
810 user_agent = g_strconcat(PLUGIN_NAME, " ", PLUGIN_VERSION, " / Geany ", GEANY_VERSION, NULL);
811 session = soup_session_async_new_with_options(SOUP_SESSION_USER_AGENT, user_agent, NULL);
812 g_free(user_agent);
813
814 debug_log_message_body(msg, msg->request_body, "Request");
815
816 status = soup_session_send_message(session, msg);
817 g_object_unref(session);
818
819 debug_log_message_body(msg, msg->response_body, "Response");
820
821 if (! SOUP_STATUS_IS_SUCCESSFUL(status))
822 {
823 show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
824 _("Failed to paste the code"),
825 _("Error pasting the code to the pastebin service %s.\n"
826 "Error code: %u (%s).\n\n%s"),
827 pastebin->name, status, msg->reason_phrase,
828 (SOUP_STATUS_IS_TRANSPORT_ERROR(status)
829 ? _("Check your connection or the pastebin configuration and retry.")
830 : SOUP_STATUS_IS_SERVER_ERROR(status)
831 ? _("Wait for the service to come back and retry, or retry "
832 "with another pastebin service.")
833 : _("Check the pastebin configuration and retry.")));
834 }
835 else
836 {
837 GError *err = NULL;
838 gchar *p_url = pastebin_parse_response(pastebin, msg, doc, f_content,
839 &err);
840
841 if (err || ! p_url)
842 {
843 show_msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
844 _("Failed to obtain paste URL."),
845 _("The code was successfully pasted on %s, but an "
846 "error occurred trying to obtain its URL: %s\n\n%s"),
847 pastebin->name,
848 (err ? err->message
849 : _("Unexpected response from the pastebin service.")),
850 _("Check the pastebin configuration and retry."));
851
852 if (err)
853 g_error_free(err);
854 }
855 else if (check_button_is_checked)
856 {
857 utils_open_browser(p_url);
858 }
859 else
860 {
861 show_msgbox(GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
862 _("Paste Successful"),
863 _("Your paste can be found here:\n<a href=\"%s\" "
864 "title=\"Click to open the paste in your browser\">%s</a>"),
865 p_url, p_url);
866 }
867
868 g_free(p_url);
869 }
870
871 g_object_unref(msg);
872 g_free(f_content);
873 }
874
item_activate(GtkMenuItem * menuitem,gpointer gdata)875 static void item_activate(GtkMenuItem * menuitem, gpointer gdata)
876 {
877 GeanyDocument *doc = document_get_current();
878
879 if(!DOC_VALID(doc))
880 {
881 dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("There are no opened documents. Open one and retry.\n"));
882 return;
883 }
884
885 paste(doc, pastebin_selected);
886 }
887
on_configure_response(GtkDialog * dialog,gint response,gpointer * user_data)888 static void on_configure_response(GtkDialog * dialog, gint response, gpointer * user_data)
889 {
890 if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
891 {
892 if(g_strcmp0(gtk_entry_get_text(GTK_ENTRY(widgets.author_entry)), "") == 0)
893 {
894 dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("The author name field is empty!"));
895 }
896 else
897 {
898 SETPTR(pastebin_selected, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(widgets.combo)));
899 check_button_is_checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets.check_button));
900 SETPTR(author_name, g_strdup(gtk_entry_get_text(GTK_ENTRY(widgets.author_entry))));
901 save_settings();
902 }
903 }
904 }
905
plugin_configure(GtkDialog * dialog)906 GtkWidget *plugin_configure(GtkDialog * dialog)
907 {
908 GSList *node;
909 gint i;
910 GtkWidget *label, *vbox, *author_label;
911
912 vbox = gtk_vbox_new(FALSE, 6);
913
914 label = gtk_label_new(_("Select a pastebin:"));
915 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
916
917 author_label = gtk_label_new(_("Enter the author name:"));
918 gtk_misc_set_alignment(GTK_MISC(author_label), 0, 0.5);
919
920 widgets.author_entry = gtk_entry_new();
921
922 if(author_name == NULL)
923 author_name = g_strdup(USERNAME);
924
925 gtk_entry_set_text(GTK_ENTRY(widgets.author_entry), author_name);
926
927 widgets.combo = gtk_combo_box_text_new();
928
929 for (i = 0, node = pastebins; node; node = node->next, i++)
930 {
931 Pastebin *pastebin = node->data;
932
933 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widgets.combo), pastebin->name);
934 if (pastebin_selected && strcmp(pastebin_selected, pastebin->name) == 0)
935 gtk_combo_box_set_active(GTK_COMBO_BOX(widgets.combo), i);
936 }
937
938 widgets.check_button = gtk_check_button_new_with_label(_("Show your paste in a new browser tab"));
939
940 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
941 gtk_box_pack_start(GTK_BOX(vbox), widgets.combo, FALSE, FALSE, 0);
942 gtk_box_pack_start(GTK_BOX(vbox), author_label, FALSE, FALSE, 0);
943 gtk_box_pack_start(GTK_BOX(vbox), widgets.author_entry, FALSE, FALSE, 0);
944 gtk_box_pack_start(GTK_BOX(vbox), widgets.check_button, FALSE, FALSE, 0);
945
946 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgets.check_button), check_button_is_checked);
947
948 gtk_widget_show_all(vbox);
949
950 g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL);
951
952 return vbox;
953 }
954
add_menu_item(void)955 static void add_menu_item(void)
956 {
957 GtkWidget *paste_item;
958
959 paste_item = gtk_menu_item_new_with_mnemonic(_("_Paste it!"));
960 gtk_widget_show(paste_item);
961 gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),
962 paste_item);
963 g_signal_connect(paste_item, "activate", G_CALLBACK(item_activate),
964 NULL);
965
966 main_menu_item = paste_item;
967 }
968
plugin_init(GeanyData * data)969 void plugin_init(GeanyData * data)
970 {
971 load_all_pastebins();
972 load_settings();
973 add_menu_item();
974 }
975
976
plugin_cleanup(void)977 void plugin_cleanup(void)
978 {
979 g_free(author_name);
980 gtk_widget_destroy(main_menu_item);
981 free_all_pastebins();
982 }
983