File: escape_utils.c

package info (click to toggle)
ruby-escape-utils 1.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 348 kB
  • ctags: 248
  • sloc: ansic: 1,718; ruby: 924; makefile: 2
file content (234 lines) | stat: -rw-r--r-- 5,894 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// tell rbx not to use it's caching compat layer
// by doing this we're making a promise to RBX that
// we'll never modify the pointers we get back from RSTRING_PTR
#define RSTRING_NOT_MODIFIED

#include <ruby.h>
#include <ruby/encoding.h>
#include "houdini.h"

static VALUE rb_eEncodingCompatibilityError;

static VALUE eu_new_str(const char *str, size_t len)
{
	return rb_enc_str_new(str, len, rb_utf8_encoding());
}

static void check_utf8_encoding(VALUE str)
{
	static rb_encoding *_cached[3] = {NULL, NULL, NULL};
	rb_encoding *enc;

	if (_cached[0] == NULL) {
		_cached[0] = rb_utf8_encoding();
		_cached[1] = rb_usascii_encoding();
		_cached[2] = rb_ascii8bit_encoding();
	}

	enc = rb_enc_get(str);
	if (enc != _cached[0] && enc != _cached[1] && enc != _cached[2]) {
		rb_raise(rb_eEncodingCompatibilityError,
			"Input must be UTF-8 or US-ASCII, %s given", rb_enc_name(enc));
	}
}

typedef int (*houdini_cb)(gh_buf *, const uint8_t *, size_t);

static VALUE rb_mEscapeUtils;
static ID ID_at_html_safe, ID_new;

/**
 * html_secure instance variable
 */
static int g_html_secure = 1;

static VALUE rb_eu_set_html_secure(VALUE self, VALUE val)
{
	g_html_secure = RTEST(val);
	rb_ivar_set(self, rb_intern("@html_secure"), val);
	return val;
}

/**
* html_safe_string_class instance variable
*/
static VALUE rb_html_safe_string_class;
static VALUE rb_html_safe_string_template_object;

static VALUE rb_eu_set_html_safe_string_class(VALUE self, VALUE val)
{
	Check_Type(val, T_CLASS);

	if (rb_funcall(val, rb_intern("<="), 1, rb_cString) == Qnil)
		rb_raise(rb_eArgError, "%s must be a descendent of String", rb_class2name(val));

	rb_html_safe_string_class = val;
	rb_html_safe_string_template_object = rb_class_new_instance(0, NULL, rb_html_safe_string_class);
	OBJ_FREEZE(rb_html_safe_string_template_object);
	rb_ivar_set(self, rb_intern("@html_safe_string_class"), val);
	return val;
}

/**
 * Generic template
 */
static VALUE
rb_eu__generic(VALUE str, houdini_cb do_escape)
{
	gh_buf buf = GH_BUF_INIT;

	if (NIL_P(str))
		return eu_new_str("", 0);

	Check_Type(str, T_STRING);
	check_utf8_encoding(str);

	if (do_escape(&buf, (const uint8_t *)RSTRING_PTR(str), RSTRING_LEN(str))) {
		VALUE result = eu_new_str(buf.ptr, buf.size);
		gh_buf_free(&buf);
		return result;
	}

	return str;
}


/**
 * HTML methods
 */
static VALUE new_html_safe_string(const char *ptr, size_t len)
{
	return rb_str_new_with_class(rb_html_safe_string_template_object, ptr, len);
}

static VALUE rb_eu_escape_html_as_html_safe(VALUE self, VALUE str)
{
	VALUE result;
	int secure = g_html_secure;
	gh_buf buf = GH_BUF_INIT;

	Check_Type(str, T_STRING);
	check_utf8_encoding(str);

	if (houdini_escape_html0(&buf, (const uint8_t *)RSTRING_PTR(str), RSTRING_LEN(str), secure)) {
		result = new_html_safe_string(buf.ptr, buf.size);
		gh_buf_free(&buf);
	} else {
		result = new_html_safe_string(RSTRING_PTR(str), RSTRING_LEN(str));
	}

	rb_ivar_set(result, ID_at_html_safe, Qtrue);

	return result;
}

static VALUE rb_eu_escape_html(int argc, VALUE *argv, VALUE self)
{
	VALUE str, rb_secure;
	gh_buf buf = GH_BUF_INIT;
	int secure = g_html_secure;

	if (rb_scan_args(argc, argv, "11", &str, &rb_secure) == 2) {
		if (rb_secure == Qfalse) {
			secure = 0;
		}
	}

	Check_Type(str, T_STRING);
	check_utf8_encoding(str);

	if (houdini_escape_html0(&buf, (const uint8_t *)RSTRING_PTR(str), RSTRING_LEN(str), secure)) {
		VALUE result = eu_new_str(buf.ptr, buf.size);
		gh_buf_free(&buf);
		return result;
	}

	return str;
}

static VALUE rb_eu_unescape_html(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_unescape_html);
}


/**
 * XML methods
 */
static VALUE rb_eu_escape_xml(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_escape_xml);
}


/**
 * JavaScript methods
 */
static VALUE rb_eu_escape_js(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_escape_js);
}

static VALUE rb_eu_unescape_js(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_unescape_js);
}


/**
 * URL methods
 */
static VALUE rb_eu_escape_url(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_escape_url);
}

static VALUE rb_eu_unescape_url(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_unescape_url);
}


/**
 * URI methods
 */
static VALUE rb_eu_escape_uri(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_escape_uri);
}

static VALUE rb_eu_unescape_uri(VALUE self, VALUE str)
{
	return rb_eu__generic(str, &houdini_unescape_uri);
}


/**
 * Ruby Extension initializer
 */
__attribute__((visibility("default")))
void Init_escape_utils()
{
	rb_eEncodingCompatibilityError = rb_const_get(rb_cEncoding, rb_intern("CompatibilityError"));

	ID_new = rb_intern("new");
	ID_at_html_safe = rb_intern("@html_safe");
	rb_global_variable(&rb_html_safe_string_class);
	rb_global_variable(&rb_html_safe_string_template_object);

	rb_mEscapeUtils = rb_define_module("EscapeUtils");
	rb_define_method(rb_mEscapeUtils, "escape_html_as_html_safe", rb_eu_escape_html_as_html_safe, 1);
	rb_define_method(rb_mEscapeUtils, "escape_html", rb_eu_escape_html, -1);
	rb_define_method(rb_mEscapeUtils, "unescape_html", rb_eu_unescape_html, 1);
	rb_define_method(rb_mEscapeUtils, "escape_xml", rb_eu_escape_xml, 1);
	rb_define_method(rb_mEscapeUtils, "escape_javascript", rb_eu_escape_js, 1);
	rb_define_method(rb_mEscapeUtils, "unescape_javascript", rb_eu_unescape_js, 1);
	rb_define_method(rb_mEscapeUtils, "escape_url", rb_eu_escape_url, 1);
	rb_define_method(rb_mEscapeUtils, "unescape_url", rb_eu_unescape_url, 1);
	rb_define_method(rb_mEscapeUtils, "escape_uri", rb_eu_escape_uri, 1);
	rb_define_method(rb_mEscapeUtils, "unescape_uri", rb_eu_unescape_uri, 1);

	rb_define_singleton_method(rb_mEscapeUtils, "html_secure=", rb_eu_set_html_secure, 1);
	rb_define_singleton_method(rb_mEscapeUtils, "html_safe_string_class=", rb_eu_set_html_safe_string_class, 1);
}