Ruby: ext/cgi/escape/escape.c Source File

Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)

escape.c
Go to the documentation of this file.
1#include "ruby.h"
2#include "ruby/encoding.h"
3
4 RUBY_EXTERN unsigned long ruby_scan_digits(const char *str, ssize_t len, int base, size_t *retlen, int *overflow);
5 RUBY_EXTERN const char ruby_hexdigits[];
6 RUBY_EXTERN const signed char ruby_digit36_to_number_table[];
7#define lower_hexdigits (ruby_hexdigits+0)
8#define upper_hexdigits (ruby_hexdigits+16)
9#define char_to_number(c) ruby_digit36_to_number_table[(unsigned char)(c)]
10
11static VALUE rb_cCGI, rb_mUtil, rb_mEscape;
12static ID id_accept_charset;
13
14#define HTML_ESCAPE_MAX_LEN 6
15
16static const struct {
17 uint8_t len;
18 char str[HTML_ESCAPE_MAX_LEN+1];
19} html_escape_table[UCHAR_MAX+1] = {
20#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str}
21 HTML_ESCAPE('\'', "&#39;"),
22 HTML_ESCAPE('&', "&amp;"),
23 HTML_ESCAPE('"', "&quot;"),
24 HTML_ESCAPE('<', "&lt;"),
25 HTML_ESCAPE('>', "&gt;"),
26#undef HTML_ESCAPE
27};
28
29static inline void
30preserve_original_state(VALUE orig, VALUE dest)
31{
32 rb_enc_associate(dest, rb_enc_get(orig));
33}
34
35static VALUE
36optimized_escape_html(VALUE str)
37{
38 VALUE vbuf;
39 typedef char escape_buf[HTML_ESCAPE_MAX_LEN];
40 char *buf = *ALLOCV_N(escape_buf, vbuf, RSTRING_LEN(str));
41 const char *cstr = RSTRING_PTR(str);
42 const char *end = cstr + RSTRING_LEN(str);
43
44 char *dest = buf;
45 while (cstr < end) {
46 const unsigned char c = *cstr++;
47 uint8_t len = html_escape_table[c].len;
48 if (len) {
49 memcpy(dest, html_escape_table[c].str, len);
50 dest += len;
51 }
52 else {
53 *dest++ = c;
54 }
55 }
56
57 VALUE escaped;
58 if (RSTRING_LEN(str) < (dest - buf)) {
59 escaped = rb_str_new(buf, dest - buf);
60 preserve_original_state(str, escaped);
61 }
62 else {
63 escaped = rb_str_dup(str);
64 }
65 ALLOCV_END(vbuf);
66 return escaped;
67}
68
69static VALUE
70optimized_unescape_html(VALUE str)
71{
72 enum {UNICODE_MAX = 0x10ffff};
73 rb_encoding *enc = rb_enc_get(str);
74 unsigned long charlimit = (strcasecmp(rb_enc_name(enc), "UTF-8") == 0 ? UNICODE_MAX :
75 strcasecmp(rb_enc_name(enc), "ISO-8859-1") == 0 ? 256 :
76 128);
77 long i, len, beg = 0;
78 size_t clen, plen;
79 int overflow;
80 const char *cstr;
81 char buf[6];
82 VALUE dest = 0;
83
84 len = RSTRING_LEN(str);
85 cstr = RSTRING_PTR(str);
86
87 for (i = 0; i < len; i++) {
88 unsigned long cc;
89 char c = cstr[i];
90 if (c != '&') continue;
91 plen = i - beg;
92 if (++i >= len) break;
93 c = (unsigned char)cstr[i];
94#define MATCH(s) (len - i >= (int)rb_strlen_lit(s) && \
95 memcmp(&cstr[i], s, rb_strlen_lit(s)) == 0 && \
96 (i += rb_strlen_lit(s) - 1, 1))
97 switch (c) {
98 case 'a':
99 ++i;
100 if (MATCH("pos;")) {
101 c = '\'';
102 }
103 else if (MATCH("mp;")) {
104 c = '&';
105 }
106 else continue;
107 break;
108 case 'q':
109 ++i;
110 if (MATCH("uot;")) {
111 c = '"';
112 }
113 else continue;
114 break;
115 case 'g':
116 ++i;
117 if (MATCH("t;")) {
118 c = '>';
119 }
120 else continue;
121 break;
122 case 'l':
123 ++i;
124 if (MATCH("t;")) {
125 c = '<';
126 }
127 else continue;
128 break;
129 case '#':
130 if (len - ++i >= 2 && ISDIGIT(cstr[i])) {
131 cc = ruby_scan_digits(&cstr[i], len-i, 10, &clen, &overflow);
132 }
133 else if ((cstr[i] == 'x' || cstr[i] == 'X') && len - ++i >= 2 && ISXDIGIT(cstr[i])) {
134 cc = ruby_scan_digits(&cstr[i], len-i, 16, &clen, &overflow);
135 }
136 else continue;
137 i += clen;
138 if (overflow || cc >= charlimit || cstr[i] != ';') continue;
139 if (!dest) {
140 dest = rb_str_buf_new(len);
141 }
142 rb_str_cat(dest, cstr + beg, plen);
143 if (charlimit > 256) {
144 rb_str_cat(dest, buf, rb_enc_mbcput((OnigCodePoint)cc, buf, enc));
145 }
146 else {
147 c = (unsigned char)cc;
148 rb_str_cat(dest, &c, 1);
149 }
150 beg = i + 1;
151 continue;
152 default:
153 --i;
154 continue;
155 }
156 if (!dest) {
157 dest = rb_str_buf_new(len);
158 }
159 rb_str_cat(dest, cstr + beg, plen);
160 rb_str_cat(dest, &c, 1);
161 beg = i + 1;
162 }
163
164 if (dest) {
165 rb_str_cat(dest, cstr + beg, len - beg);
166 preserve_original_state(str, dest);
167 return dest;
168 }
169 else {
170 return rb_str_dup(str);
171 }
172}
173
174static unsigned char
175url_unreserved_char(unsigned char c)
176{
177 switch (c) {
178 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
179 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j':
180 case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't':
181 case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
182 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J':
183 case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T':
184 case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
185 case '-': case '.': case '_': case '~':
186 return 1;
187 default:
188 break;
189 }
190 return 0;
191}
192
193static VALUE
194optimized_escape(VALUE str)
195{
196 long i, len, beg = 0;
197 VALUE dest = 0;
198 const char *cstr;
199 char buf[4] = {'%'};
200
201 len = RSTRING_LEN(str);
202 cstr = RSTRING_PTR(str);
203
204 for (i = 0; i < len; ++i) {
205 const unsigned char c = (unsigned char)cstr[i];
206 if (!url_unreserved_char(c)) {
207 if (!dest) {
208 dest = rb_str_buf_new(len);
209 }
210
211 rb_str_cat(dest, cstr + beg, i - beg);
212 beg = i + 1;
213
214 if (c == ' ') {
215 rb_str_cat_cstr(dest, "+");
216 }
217 else {
218 buf[1] = upper_hexdigits[(c >> 4) & 0xf];
219 buf[2] = upper_hexdigits[c & 0xf];
220 rb_str_cat(dest, buf, 3);
221 }
222 }
223 }
224
225 if (dest) {
226 rb_str_cat(dest, cstr + beg, len - beg);
227 preserve_original_state(str, dest);
228 return dest;
229 }
230 else {
231 return rb_str_dup(str);
232 }
233}
234
235static VALUE
236optimized_unescape(VALUE str, VALUE encoding)
237{
238 long i, len, beg = 0;
239 VALUE dest = 0;
240 const char *cstr;
241 rb_encoding *enc = rb_to_encoding(encoding);
242 int cr, origenc, encidx = rb_enc_to_index(enc);
243
244 len = RSTRING_LEN(str);
245 cstr = RSTRING_PTR(str);
246
247 for (i = 0; i < len; ++i) {
248 char buf[1];
249 const char c = cstr[i];
250 int clen = 0;
251 if (c == '%') {
252 if (i + 3 > len) break;
253 if (!ISXDIGIT(cstr[i+1])) continue;
254 if (!ISXDIGIT(cstr[i+2])) continue;
255 buf[0] = ((char_to_number(cstr[i+1]) << 4)
256 | char_to_number(cstr[i+2]));
257 clen = 2;
258 }
259 else if (c == '+') {
260 buf[0] = ' ';
261 }
262 else {
263 continue;
264 }
265
266 if (!dest) {
267 dest = rb_str_buf_new(len);
268 }
269
270 rb_str_cat(dest, cstr + beg, i - beg);
271 i += clen;
272 beg = i + 1;
273
274 rb_str_cat(dest, buf, 1);
275 }
276
277 if (dest) {
278 rb_str_cat(dest, cstr + beg, len - beg);
279 preserve_original_state(str, dest);
280 cr = ENC_CODERANGE_UNKNOWN;
281 }
282 else {
283 dest = rb_str_dup(str);
284 cr = ENC_CODERANGE(str);
285 }
286 origenc = rb_enc_get_index(str);
287 if (origenc != encidx) {
288 rb_enc_associate_index(dest, encidx);
289 if (!ENC_CODERANGE_CLEAN_P(rb_enc_str_coderange(dest))) {
290 rb_enc_associate_index(dest, origenc);
291 if (cr != ENC_CODERANGE_UNKNOWN)
292 ENC_CODERANGE_SET(dest, cr);
293 }
294 }
295 return dest;
296}
297
298/*
299 * call-seq:
300 * CGI.escapeHTML(string) -> string
301 *
302 * Returns HTML-escaped string.
303 *
304 */
305static VALUE
306cgiesc_escape_html(VALUE self, VALUE str)
307{
308 StringValue(str);
309
310 if (rb_enc_str_asciicompat_p(str)) {
311 return optimized_escape_html(str);
312 }
313 else {
314 return rb_call_super(1, &str);
315 }
316}
317
318/*
319 * call-seq:
320 * CGI.unescapeHTML(string) -> string
321 *
322 * Returns HTML-unescaped string.
323 *
324 */
325static VALUE
326cgiesc_unescape_html(VALUE self, VALUE str)
327{
328 StringValue(str);
329
330 if (rb_enc_str_asciicompat_p(str)) {
331 return optimized_unescape_html(str);
332 }
333 else {
334 return rb_call_super(1, &str);
335 }
336}
337
338/*
339 * call-seq:
340 * CGI.escape(string) -> string
341 *
342 * Returns URL-escaped string.
343 *
344 */
345static VALUE
346cgiesc_escape(VALUE self, VALUE str)
347{
348 StringValue(str);
349
350 if (rb_enc_str_asciicompat_p(str)) {
351 return optimized_escape(str);
352 }
353 else {
354 return rb_call_super(1, &str);
355 }
356}
357
358static VALUE
359accept_charset(int argc, VALUE *argv, VALUE self)
360{
361 if (argc > 0)
362 return argv[0];
363 return rb_cvar_get(CLASS_OF(self), id_accept_charset);
364}
365
366/*
367 * call-seq:
368 * CGI.unescape(string, encoding=@@accept_charset) -> string
369 *
370 * Returns URL-unescaped string.
371 *
372 */
373static VALUE
374cgiesc_unescape(int argc, VALUE *argv, VALUE self)
375{
376 VALUE str = (rb_check_arity(argc, 1, 2), argv[0]);
377
378 StringValue(str);
379
380 if (rb_enc_str_asciicompat_p(str)) {
381 VALUE enc = accept_charset(argc-1, argv+1, self);
382 return optimized_unescape(str, enc);
383 }
384 else {
385 return rb_call_super(argc, argv);
386 }
387}
388
389void
390Init_escape(void)
391{
392#ifdef HAVE_RB_EXT_RACTOR_SAFE
393 rb_ext_ractor_safe(true);
394#endif
395
396 id_accept_charset = rb_intern_const("@@accept_charset");
397 InitVM(escape);
398}
399
400void
401InitVM_escape(void)
402{
403 rb_cCGI = rb_define_class("CGI", rb_cObject);
404 rb_mEscape = rb_define_module_under(rb_cCGI, "Escape");
405 rb_mUtil = rb_define_module_under(rb_cCGI, "Util");
406 rb_define_method(rb_mEscape, "escapeHTML", cgiesc_escape_html, 1);
407 rb_define_method(rb_mEscape, "unescapeHTML", cgiesc_unescape_html, 1);
408 rb_define_method(rb_mEscape, "escape", cgiesc_escape, 1);
409 rb_define_method(rb_mEscape, "unescape", cgiesc_unescape, -1);
410 rb_prepend_module(rb_mUtil, rb_mEscape);
411 rb_extend_object(rb_cCGI, rb_mEscape);
412}
ISDIGIT
#define ISDIGIT
Definition: ctype.h:43
ISXDIGIT
#define ISXDIGIT
Definition: ctype.h:44
rb_define_method
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
Definition: cxxanyargs.hpp:653
RUBY_EXTERN
#define RUBY_EXTERN
Definition: dllexport.h:36
rb_enc_get_index
int rb_enc_get_index(VALUE obj)
Definition: encoding.c:977
rb_enc_associate
VALUE rb_enc_associate(VALUE obj, rb_encoding *enc)
Definition: encoding.c:1064
rb_enc_get
rb_encoding * rb_enc_get(VALUE obj)
Definition: encoding.c:1070
rb_enc_to_index
int rb_enc_to_index(rb_encoding *enc)
Definition: encoding.c:197
rb_to_encoding
rb_encoding * rb_to_encoding(VALUE enc)
Definition: encoding.c:329
rb_enc_associate_index
VALUE rb_enc_associate_index(VALUE obj, int idx)
Definition: encoding.c:1036
MATCH
#define MATCH(s)
Init_escape
void Init_escape(void)
Definition: escape.c:390
len
uint8_t len
Definition: escape.c:17
ruby_digit36_to_number_table
const signed char ruby_digit36_to_number_table[]
Definition: util.c:79
HTML_ESCAPE
#define HTML_ESCAPE(c, str)
str
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
upper_hexdigits
#define upper_hexdigits
Definition: escape.c:8
ruby_hexdigits
const char ruby_hexdigits[]
Definition: util.c:34
InitVM_escape
void InitVM_escape(void)
Definition: escape.c:401
ruby_scan_digits
unsigned long ruby_scan_digits(const char *str, ssize_t len, int base, size_t *retlen, int *overflow)
Definition: util.c:101
HTML_ESCAPE_MAX_LEN
#define HTML_ESCAPE_MAX_LEN
Definition: escape.c:14
char_to_number
#define char_to_number(c)
Definition: escape.c:9
RSTRING_LEN
#define RSTRING_LEN(string)
Definition: fbuffer.h:22
RSTRING_PTR
#define RSTRING_PTR(string)
Definition: fbuffer.h:19
memcpy
#define memcpy(d, s, n)
Definition: ffi_common.h:55
CLASS_OF
#define CLASS_OF
Definition: globals.h:153
rb_extend_object
void rb_extend_object(VALUE obj, VALUE module)
Extend the object with the module.
Definition: eval.c:1730
rb_define_class
VALUE rb_define_class(const char *name, VALUE super)
Defines a top-level class.
Definition: class.c:748
rb_prepend_module
void rb_prepend_module(VALUE klass, VALUE module)
Definition: class.c:1170
rb_define_module_under
VALUE rb_define_module_under(VALUE outer, const char *name)
Definition: class.c:895
rb_cObject
VALUE rb_cObject
Object class.
Definition: object.c:49
encoding.h
rb_enc_mbcput
#define rb_enc_mbcput(c, buf, enc)
Definition: encoding.h:208
ENC_CODERANGE_CLEAN_P
#define ENC_CODERANGE_CLEAN_P(cr)
Definition: encoding.h:96
rb_enc_str_asciicompat_p
#define rb_enc_str_asciicompat_p(str)
Definition: encoding.h:248
rb_enc_str_coderange
int rb_enc_str_coderange(VALUE)
Definition: string.c:725
ENC_CODERANGE
#define ENC_CODERANGE(obj)
Definition: encoding.h:97
ENC_CODERANGE_UNKNOWN
#define ENC_CODERANGE_UNKNOWN
Definition: encoding.h:92
rb_enc_name
#define rb_enc_name(enc)
Definition: encoding.h:168
ENC_CODERANGE_SET
#define ENC_CODERANGE_SET(obj, cr)
Definition: encoding.h:99
rb_call_super
VALUE rb_call_super(int, const VALUE *)
Definition: vm_eval.c:298
rb_check_arity
#define rb_check_arity
Definition: error.h:34
rb_ext_ractor_safe
void rb_ext_ractor_safe(bool flag)
Definition: load.c:1058
rb_str_new
#define rb_str_new(str, len)
Definition: string.h:213
rb_str_cat
VALUE rb_str_cat(VALUE, const char *, long)
Definition: string.c:2962
rb_str_buf_new
VALUE rb_str_buf_new(long)
Definition: string.c:1398
rb_str_cat_cstr
#define rb_str_cat_cstr(buf, str)
Definition: string.h:266
rb_str_dup
VALUE rb_str_dup(VALUE)
Definition: string.c:1631
rb_cvar_get
VALUE rb_cvar_get(VALUE, ID)
Definition: variable.c:3371
buf
voidpf void * buf
Definition: ioapi.h:138
ALLOCV_N
#define ALLOCV_N
Definition: memory.h:139
ALLOCV_END
#define ALLOCV_END
Definition: memory.h:140
UNICODE_MAX
#define UNICODE_MAX
Definition: nkf.c:427
OnigCodePoint
unsigned int OnigCodePoint
Definition: onigmo.h:80
StringValue
#define StringValue(v)
Definition: rstring.h:50
ruby.h
InitVM
#define InitVM(ext)
Definition: ruby.h:112
argc
int argc
Definition: ruby.c:240
argv
char ** argv
Definition: ruby.c:241
uint8_t
unsigned char uint8_t
Definition: sha2.h:100
OnigEncodingTypeST
Definition: onigmo.h:160
VALUE
unsigned long VALUE
Definition: value.h:38
ID
unsigned long ID
Definition: value.h:39
strcasecmp
#define strcasecmp
Definition: win32.h:207