1 /* Ruby scripting hooks */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include <ruby.h>
8 
9 #include "elinks.h"
10 
11 #include "cache/cache.h"
12 #include "main/event.h"
13 #include "protocol/uri.h"
14 #include "scripting/ruby/core.h"
15 #include "scripting/ruby/hooks.h"
16 #include "session/location.h"
17 #include "session/session.h"
18 #include "util/string.h"
19 
20 
21 /* The events that will trigger the functions below and what they are expected
22  * to do is explained in doc/events.txt */
23 
24 /* We need to catch and handle errors because, otherwise, Ruby will kill us. */
25 
26 struct erb_protect_info {
27 	unsigned char *name;
28 	int argc;
29 	VALUE *args;
30 };
31 
32 static VALUE
do_erb_protected_method_call(VALUE data)33 do_erb_protected_method_call(VALUE data)
34 {
35 	struct erb_protect_info *info = (struct erb_protect_info *) data;
36 	ID method_id;
37 
38 	assert(info);
39 
40 	method_id = rb_intern(info->name);
41 
42 	return rb_funcall3(erb_module, method_id, info->argc, info->args);
43 }
44 
45 static VALUE
erb_protected_method_call(unsigned char * name,int argc,VALUE * args,int * error)46 erb_protected_method_call(unsigned char *name, int argc, VALUE *args, int *error)
47 {
48 	struct erb_protect_info info = { name, argc, args };
49 
50 	return rb_protect(do_erb_protected_method_call, (VALUE) &info, error);
51 }
52 
53 
54 
55 static enum evhook_status
script_hook_goto_url(va_list ap,void * data)56 script_hook_goto_url(va_list ap, void *data)
57 {
58 	unsigned char **url = va_arg(ap, unsigned char **);
59 	struct session *ses = va_arg(ap, struct session *);
60 	int error;
61 	VALUE args[2];
62 	VALUE result;
63 
64 	if (*url == NULL)
65 		return EVENT_HOOK_STATUS_NEXT;
66 
67 	args[0] = rb_str_new2(*url);
68 
69 	if (!ses || !have_location(ses)) {
70 		args[1] = Qnil;
71 	} else {
72 		args[1] = rb_str_new2(struri(cur_loc(ses)->vs.uri));
73 	}
74 
75 	result = erb_protected_method_call("goto_url_hook", 2, args, &error);
76 	if (error) {
77 		erb_report_error(ses, error);
78 		return EVENT_HOOK_STATUS_NEXT;
79 	}
80 
81 	switch (rb_type(result)) {
82 	case T_STRING:
83 	{
84 		unsigned char *new_url;
85 
86 		new_url = memacpy(RSTRING(result)->ptr, RSTRING(result)->len);
87 		if (new_url) {
88 			mem_free_set(url, new_url);
89 		}
90 		break;
91 	}
92 	case T_NIL:
93 		break;
94 
95 	default:
96 		alert_ruby_error(ses, "goto_url_hook must return a string or nil");
97 	}
98 
99 	return EVENT_HOOK_STATUS_NEXT;
100 }
101 
102 static enum evhook_status
script_hook_follow_url(va_list ap,void * data)103 script_hook_follow_url(va_list ap, void *data)
104 {
105 	unsigned char **url = va_arg(ap, unsigned char **);
106 	struct session *ses = va_arg(ap, struct session *);
107 	int error;
108 	VALUE args[1];
109 	VALUE result;
110 
111 	evhook_use_params(url && ses);
112 
113 	if (*url == NULL)
114 		return EVENT_HOOK_STATUS_NEXT;
115 
116 	args[0] = rb_str_new2(*url);
117 
118 	result = erb_protected_method_call("follow_url_hook", 1, args, &error);
119 	if (error) {
120 		erb_report_error(ses, error);
121 		return EVENT_HOOK_STATUS_NEXT;
122 	}
123 
124 	switch (rb_type(result)) {
125 	case T_STRING:
126 	{
127 		unsigned char *new_url;
128 
129 		new_url = memacpy(RSTRING(result)->ptr, RSTRING(result)->len);
130 		if (new_url) {
131 			mem_free_set(url, new_url);
132 		}
133 		break;
134 	}
135 	case T_NIL:
136 		break;
137 
138 	default:
139 		alert_ruby_error(ses, "follow_url_hook must return a string or nil");
140 	}
141 
142 	return EVENT_HOOK_STATUS_NEXT;
143 }
144 
145 static enum evhook_status
script_hook_pre_format_html(va_list ap,void * data)146 script_hook_pre_format_html(va_list ap, void *data)
147 {
148 	struct session *ses = va_arg(ap, struct session *);
149 	struct cache_entry *cached = va_arg(ap, struct cache_entry *);
150 	struct fragment *fragment = get_cache_fragment(cached);
151 	unsigned char *url = struri(cached->uri);
152 	int error;
153 	VALUE args[2];
154 	VALUE result;
155 
156 	evhook_use_params(ses && cached);
157 
158 	if (!cached->length || !*fragment->data)
159 		return EVENT_HOOK_STATUS_NEXT;
160 
161 	args[0] = rb_str_new2(url);
162 	/* FIXME: Use html_len */
163 	args[1] = rb_str_new2(fragment->data);
164 
165 	result = erb_protected_method_call("pre_format_html_hook", 2, args, &error);
166 	if (error) {
167 		erb_report_error(ses, error);
168 		return EVENT_HOOK_STATUS_NEXT;
169 	}
170 
171 	switch (rb_type(result)) {
172 	case T_STRING:
173 	{
174 		int len = RSTRING(result)->len;
175 
176 		add_fragment(cached, 0, RSTRING(result)->ptr, len);
177 		normalize_cache_entry(cached, len);
178 
179 		break;
180 	}
181 	case T_NIL:
182 		break;
183 
184 	default:
185 		alert_ruby_error(ses, "pre_format_html_hook must return a string or nil");
186 	}
187 
188 	return EVENT_HOOK_STATUS_NEXT;
189 }
190 
191 /* The Ruby function can return:
192  *  - "PROXY:PORT" to use the specified proxy
193  *  - ""           to not use any proxy
194  *  - nil          to use the default proxies */
195 static enum evhook_status
script_hook_get_proxy(va_list ap,void * data)196 script_hook_get_proxy(va_list ap, void *data)
197 {
198 	unsigned char **new_proxy_url = va_arg(ap, unsigned char **);
199 	unsigned char *url = va_arg(ap, unsigned char *);
200 	int error;
201 	VALUE args[1];
202 	VALUE result;
203 
204 	if (!new_proxy_url || !url)
205 		return EVENT_HOOK_STATUS_NEXT;
206 
207 	args[0] = rb_str_new2(url);
208 
209 	result = erb_protected_method_call("proxy_hook", 1, args, &error);
210 	if (error) {
211 		erb_report_error(NULL, error);
212 		return EVENT_HOOK_STATUS_NEXT;
213 	}
214 
215 	switch (rb_type(result)) {
216 	case T_STRING:
217 	{
218 		unsigned char *proxy;
219 
220 		proxy = memacpy(RSTRING(result)->ptr, RSTRING(result)->len);
221 		if (proxy) {
222 			mem_free_set(new_proxy_url, proxy);
223 		}
224 		break;
225 	}
226 	case T_NIL:
227 		break;
228 
229 	default:
230 		alert_ruby_error(NULL, "proxy_hook must return a string or nil");
231 	}
232 
233 	return EVENT_HOOK_STATUS_NEXT;
234 }
235 
236 static enum evhook_status
script_hook_quit(va_list ap,void * data)237 script_hook_quit(va_list ap, void *data)
238 {
239 	VALUE args[1];
240 	int error;
241 
242 	erb_protected_method_call("quit_hook", 0, args, &error);
243 	if (error)
244 		erb_report_error(NULL, error);
245 
246 	return EVENT_HOOK_STATUS_NEXT;
247 }
248 
249 struct event_hook_info ruby_scripting_hooks[] = {
250 	{ "goto-url",        0, script_hook_goto_url,        NULL },
251 	{ "follow-url",      0, script_hook_follow_url,      NULL },
252 	{ "pre-format-html", 0, script_hook_pre_format_html, NULL },
253 	{ "get-proxy",       0, script_hook_get_proxy,       NULL },
254 	{ "quit",            0, script_hook_quit,            NULL },
255 
256 	NULL_EVENT_HOOK_INFO,
257 };
258