selma 0.4.15 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +145 -179
- data/README.md +1 -0
- data/ext/selma/Cargo.toml +1 -1
- data/ext/selma/src/html/element.rs +68 -15
- data/ext/selma/src/html/end_tag.rs +3 -2
- data/ext/selma/src/html/text_chunk.rs +13 -11
- data/ext/selma/src/html.rs +3 -6
- data/ext/selma/src/lib.rs +7 -4
- data/ext/selma/src/native_ref_wrap.rs +1 -1
- data/ext/selma/src/rewriter.rs +103 -103
- data/ext/selma/src/sanitizer.rs +20 -20
- data/ext/selma/src/selector.rs +15 -15
- data/lib/selma/version.rb +1 -1
- metadata +2 -2
data/ext/selma/src/rewriter.rs
CHANGED
|
@@ -5,7 +5,7 @@ use lol_html::{
|
|
|
5
5
|
Settings,
|
|
6
6
|
};
|
|
7
7
|
use magnus::{
|
|
8
|
-
|
|
8
|
+
function, gc, method,
|
|
9
9
|
r_hash::ForEach,
|
|
10
10
|
scan_args,
|
|
11
11
|
typed_data::Obj,
|
|
@@ -34,7 +34,11 @@ use crate::{
|
|
|
34
34
|
#[derive(Clone)]
|
|
35
35
|
pub struct Handler {
|
|
36
36
|
rb_handler: Opaque<Value>,
|
|
37
|
-
|
|
37
|
+
// Store the selector's data by value (Rust-owned). The `#selector` method may return a
|
|
38
|
+
// freshly-built `Selma::Selector` that nothing else on the Ruby side references; keeping a
|
|
39
|
+
// `Obj`/`Opaque` handle to it would make its lifetime depend on GC (use-after-free if it is
|
|
40
|
+
// collected while the Rewriter is alive). We only ever read Rust data off it, so clone it.
|
|
41
|
+
selector: SelmaSelector,
|
|
38
42
|
// total_element_handler_calls: usize,
|
|
39
43
|
// total_elapsed_element_handlers: f64,
|
|
40
44
|
|
|
@@ -43,7 +47,8 @@ pub struct Handler {
|
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
struct RewriterOptions {
|
|
46
|
-
|
|
50
|
+
max_allowed_memory_usage: usize,
|
|
51
|
+
preallocated_parsing_buffer_size: usize,
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
pub struct Rewriter {
|
|
@@ -84,12 +89,13 @@ impl SelmaRewriter {
|
|
|
84
89
|
/// @return [Selma::Rewriter]
|
|
85
90
|
fn new(args: &[Value]) -> Result<Self, magnus::Error> {
|
|
86
91
|
let (rb_sanitizer, rb_handlers, rb_options) = Self::scan_parse_args(args)?;
|
|
92
|
+
let ruby = Ruby::get().unwrap();
|
|
87
93
|
|
|
88
94
|
let sanitizer = match rb_sanitizer {
|
|
89
95
|
None => {
|
|
90
96
|
// no `sanitizer:` kwarg provided, use default
|
|
91
97
|
let default_sanitizer = SelmaSanitizer::new(&[])?;
|
|
92
|
-
let wrapped_sanitizer =
|
|
98
|
+
let wrapped_sanitizer = ruby.obj_wrap(default_sanitizer);
|
|
93
99
|
// wrapped_sanitizer.funcall::<&str, (), Value>("setup", ())?;
|
|
94
100
|
Some(wrapped_sanitizer.deref().to_owned())
|
|
95
101
|
}
|
|
@@ -106,7 +112,7 @@ impl SelmaRewriter {
|
|
|
106
112
|
if !rb_handler.respond_to("selector", true).unwrap() {
|
|
107
113
|
let classname = unsafe { rb_handler.classname() };
|
|
108
114
|
return Err(magnus::Error::new(
|
|
109
|
-
|
|
115
|
+
ruby.exception_no_method_error(),
|
|
110
116
|
format!(
|
|
111
117
|
"Could not call #selector on {classname:?}; is this an object that defines it?",
|
|
112
118
|
|
|
@@ -117,7 +123,7 @@ impl SelmaRewriter {
|
|
|
117
123
|
let rb_selector: Obj<SelmaSelector> = match rb_handler.funcall("selector", ()) {
|
|
118
124
|
Err(err) => {
|
|
119
125
|
return Err(magnus::Error::new(
|
|
120
|
-
|
|
126
|
+
ruby.exception_type_error(),
|
|
121
127
|
format!("Error instantiating selector: {err:?}"),
|
|
122
128
|
));
|
|
123
129
|
}
|
|
@@ -125,7 +131,9 @@ impl SelmaRewriter {
|
|
|
125
131
|
};
|
|
126
132
|
let handler = Handler {
|
|
127
133
|
rb_handler: Opaque::from(rb_handler),
|
|
128
|
-
|
|
134
|
+
// clone the selector's data out of the Ruby object right away so the
|
|
135
|
+
// Handler no longer depends on that object surviving GC (see struct docs)
|
|
136
|
+
selector: (*rb_selector).clone(),
|
|
129
137
|
// total_element_handler_calls: 0,
|
|
130
138
|
// total_elapsed_element_handlers: 0.0,
|
|
131
139
|
|
|
@@ -140,7 +148,7 @@ impl SelmaRewriter {
|
|
|
140
148
|
|
|
141
149
|
if sanitizer.is_none() && handlers.is_empty() {
|
|
142
150
|
return Err(magnus::Error::new(
|
|
143
|
-
|
|
151
|
+
ruby.exception_arg_error(),
|
|
144
152
|
"Must provide a sanitizer or a handler",
|
|
145
153
|
));
|
|
146
154
|
}
|
|
@@ -151,58 +159,55 @@ impl SelmaRewriter {
|
|
|
151
159
|
None => {}
|
|
152
160
|
Some(options) => {
|
|
153
161
|
options.foreach(|key: Symbol, value: RHash| {
|
|
162
|
+
let ruby = Ruby::get().unwrap();
|
|
154
163
|
let key = key.to_string();
|
|
155
164
|
match key.as_str() {
|
|
156
165
|
"memory" => {
|
|
157
|
-
let max_allowed_memory_usage = value.get(
|
|
158
|
-
if max_allowed_memory_usage.is_some() {
|
|
159
|
-
let max_allowed_memory_usage = max_allowed_memory_usage.unwrap();
|
|
166
|
+
if let Some(max_allowed_memory_usage) = value.get(ruby.to_symbol("max_allowed_memory_usage")) {
|
|
160
167
|
let max_allowed_memory_usage =
|
|
161
168
|
Integer::from_value(max_allowed_memory_usage);
|
|
162
|
-
if max_allowed_memory_usage
|
|
163
|
-
match max_allowed_memory_usage.
|
|
169
|
+
if let Some(max_allowed_memory_usage) = max_allowed_memory_usage {
|
|
170
|
+
match max_allowed_memory_usage.to_u64() {
|
|
164
171
|
Ok(max_allowed_memory_usage) => {
|
|
165
|
-
rewriter_options.
|
|
172
|
+
rewriter_options.max_allowed_memory_usage =
|
|
166
173
|
max_allowed_memory_usage as usize;
|
|
167
174
|
}
|
|
168
175
|
Err(_e) => {
|
|
169
176
|
return Err(magnus::Error::new(
|
|
170
|
-
|
|
177
|
+
ruby.exception_arg_error(),
|
|
171
178
|
"max_allowed_memory_usage must be a positive integer",
|
|
172
179
|
));
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
} else {
|
|
176
|
-
rewriter_options.
|
|
183
|
+
rewriter_options.max_allowed_memory_usage = RewriterOptions::DEFAULT_MAX_ALLOWED_MEMORY_USAGE;
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
let preallocated_parsing_buffer_size = value.get(
|
|
181
|
-
if preallocated_parsing_buffer_size.is_some() {
|
|
182
|
-
let preallocated_parsing_buffer_size = preallocated_parsing_buffer_size.unwrap();
|
|
187
|
+
if let Some(preallocated_parsing_buffer_size) = value.get(ruby.to_symbol("preallocated_parsing_buffer_size")) {
|
|
183
188
|
let preallocated_parsing_buffer_size =
|
|
184
189
|
Integer::from_value(preallocated_parsing_buffer_size);
|
|
185
|
-
if preallocated_parsing_buffer_size
|
|
186
|
-
match preallocated_parsing_buffer_size.
|
|
190
|
+
if let Some(preallocated_parsing_buffer_size) = preallocated_parsing_buffer_size {
|
|
191
|
+
match preallocated_parsing_buffer_size.to_u64() {
|
|
187
192
|
Ok(preallocated_parsing_buffer_size) => {
|
|
188
|
-
rewriter_options.
|
|
193
|
+
rewriter_options.preallocated_parsing_buffer_size =
|
|
189
194
|
preallocated_parsing_buffer_size as usize;
|
|
190
195
|
}
|
|
191
196
|
Err(_e) => {
|
|
192
197
|
return Err(magnus::Error::new(
|
|
193
|
-
|
|
198
|
+
ruby.exception_arg_error(),
|
|
194
199
|
"preallocated_parsing_buffer_size must be a positive integer",
|
|
195
200
|
));
|
|
196
201
|
}
|
|
197
202
|
}
|
|
198
203
|
} else {
|
|
199
|
-
rewriter_options.
|
|
204
|
+
rewriter_options.preallocated_parsing_buffer_size = RewriterOptions::DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE;
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
_ => {
|
|
204
209
|
return Err(magnus::Error::new(
|
|
205
|
-
|
|
210
|
+
ruby.exception_arg_error(),
|
|
206
211
|
format!("Unknown option: {key:?}"),
|
|
207
212
|
));
|
|
208
213
|
}
|
|
@@ -212,13 +217,11 @@ impl SelmaRewriter {
|
|
|
212
217
|
}
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
if rewriter_options
|
|
216
|
-
.
|
|
217
|
-
.preallocated_parsing_buffer_size
|
|
218
|
-
> rewriter_options.memory_options.max_allowed_memory_usage
|
|
220
|
+
if rewriter_options.preallocated_parsing_buffer_size
|
|
221
|
+
> rewriter_options.max_allowed_memory_usage
|
|
219
222
|
{
|
|
220
223
|
return Err(magnus::Error::new(
|
|
221
|
-
|
|
224
|
+
ruby.exception_arg_error(),
|
|
222
225
|
"max_allowed_memory_usage must be greater than preallocated_parsing_buffer_size",
|
|
223
226
|
));
|
|
224
227
|
}
|
|
@@ -305,7 +308,7 @@ impl SelmaRewriter {
|
|
|
305
308
|
None => match String::from_utf8(rewritten_html) {
|
|
306
309
|
Ok(output) => Ok(output),
|
|
307
310
|
Err(err) => Err(magnus::Error::new(
|
|
308
|
-
|
|
311
|
+
Ruby::get().unwrap().exception_runtime_error(),
|
|
309
312
|
format!("{err:?}"),
|
|
310
313
|
)),
|
|
311
314
|
},
|
|
@@ -342,7 +345,7 @@ impl SelmaRewriter {
|
|
|
342
345
|
Ok(rewritten_html) => match String::from_utf8(rewritten_html) {
|
|
343
346
|
Ok(output) => Ok(output),
|
|
344
347
|
Err(err) => Err(magnus::Error::new(
|
|
345
|
-
|
|
348
|
+
Ruby::get().unwrap().exception_runtime_error(),
|
|
346
349
|
format!("{err:?}"),
|
|
347
350
|
)),
|
|
348
351
|
},
|
|
@@ -366,55 +369,42 @@ impl SelmaRewriter {
|
|
|
366
369
|
handlers.iter().for_each(|handler| {
|
|
367
370
|
let element_stack: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(vec![]));
|
|
368
371
|
|
|
369
|
-
let
|
|
370
|
-
|
|
371
|
-
let selector = ruby.get_inner(handler.rb_selector);
|
|
372
|
+
let selector = &handler.selector;
|
|
372
373
|
|
|
373
374
|
// TODO: test final raise by simulating errors
|
|
374
|
-
if selector.match_element()
|
|
375
|
+
if let Some(match_element) = selector.match_element() {
|
|
375
376
|
let closure_element_stack = element_stack.clone();
|
|
376
377
|
|
|
377
|
-
element_content_handlers.push(element!(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
)
|
|
385
|
-
Ok(_) => Ok(()),
|
|
386
|
-
Err(err) => Err(err.to_string().into()),
|
|
387
|
-
}
|
|
378
|
+
element_content_handlers.push(element!(match_element, move |el| {
|
|
379
|
+
match Self::process_element_handlers(
|
|
380
|
+
handler,
|
|
381
|
+
el,
|
|
382
|
+
&closure_element_stack.borrow(),
|
|
383
|
+
) {
|
|
384
|
+
Ok(_) => Ok(()),
|
|
385
|
+
Err(err) => Err(err.to_string().into()),
|
|
388
386
|
}
|
|
389
|
-
));
|
|
387
|
+
}));
|
|
390
388
|
}
|
|
391
389
|
|
|
392
|
-
if selector.match_text_within()
|
|
390
|
+
if let Some(match_text_within) = selector.match_text_within() {
|
|
393
391
|
let closure_element_stack = element_stack.clone();
|
|
394
392
|
|
|
395
|
-
element_content_handlers.push(text!(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if selector
|
|
403
|
-
.ignore_text_within()
|
|
404
|
-
.unwrap()
|
|
405
|
-
.iter()
|
|
406
|
-
.any(|t| element_stack.contains(t))
|
|
407
|
-
{
|
|
408
|
-
return Ok(());
|
|
409
|
-
}
|
|
393
|
+
element_content_handlers.push(text!(match_text_within, move |text| {
|
|
394
|
+
let element_stack = closure_element_stack.as_ref().borrow();
|
|
395
|
+
// check if current tag is a tag we should be ignoring text within;
|
|
396
|
+
// also checks if tag is within an ancestery of ignored tags
|
|
397
|
+
if let Some(ignore_text_within) = selector.ignore_text_within() {
|
|
398
|
+
if ignore_text_within.iter().any(|t| element_stack.contains(t)) {
|
|
399
|
+
return Ok(());
|
|
410
400
|
}
|
|
401
|
+
}
|
|
411
402
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
403
|
+
match Self::process_text_handlers(handler, text) {
|
|
404
|
+
Ok(_) => Ok(()),
|
|
405
|
+
Err(err) => Err(err.to_string().into()),
|
|
416
406
|
}
|
|
417
|
-
));
|
|
407
|
+
}));
|
|
418
408
|
}
|
|
419
409
|
|
|
420
410
|
// we need to check *every* element we iterate over, to create a stack of elements
|
|
@@ -430,14 +420,12 @@ impl SelmaRewriter {
|
|
|
430
420
|
|
|
431
421
|
let closure_element_stack = element_stack.clone();
|
|
432
422
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
)));
|
|
440
|
-
}
|
|
423
|
+
let handler: lol_html::EndTagHandler<'static> = Box::new(move |_end_tag| {
|
|
424
|
+
closure_element_stack.as_ref().borrow_mut().pop();
|
|
425
|
+
Ok(())
|
|
426
|
+
});
|
|
427
|
+
// ignore void elements (lol_html's void list may differ from selma's `self_closing`)
|
|
428
|
+
let _ = el.on_end_tag(handler);
|
|
441
429
|
|
|
442
430
|
Ok(())
|
|
443
431
|
}));
|
|
@@ -460,20 +448,21 @@ impl SelmaRewriter {
|
|
|
460
448
|
let binding = &self.0.borrow();
|
|
461
449
|
let mut output = vec![];
|
|
462
450
|
{
|
|
463
|
-
let mut
|
|
464
|
-
Settings
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
451
|
+
let mut settings =
|
|
452
|
+
Settings::new().with_memory_settings(Self::get_memory_options(binding));
|
|
453
|
+
for handler in document_content_handlers {
|
|
454
|
+
settings = settings.append_document_content_handler(handler);
|
|
455
|
+
}
|
|
456
|
+
for handler in element_content_handlers {
|
|
457
|
+
settings = settings.append_element_content_handler(handler);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let mut rewriter = HtmlRewriter::new(settings, |c: &[u8]| output.extend_from_slice(c));
|
|
472
461
|
match rewriter.write(html) {
|
|
473
462
|
Ok(_) => {}
|
|
474
463
|
Err(err) => {
|
|
475
464
|
return Err(magnus::Error::new(
|
|
476
|
-
|
|
465
|
+
Ruby::get().unwrap().exception_runtime_error(),
|
|
477
466
|
format!("{err:?}"),
|
|
478
467
|
));
|
|
479
468
|
}
|
|
@@ -487,15 +476,13 @@ impl SelmaRewriter {
|
|
|
487
476
|
element: &mut Element,
|
|
488
477
|
ancestors: &[String],
|
|
489
478
|
) -> Result<(), magnus::Error> {
|
|
490
|
-
let
|
|
479
|
+
let ruby = Ruby::get().unwrap();
|
|
480
|
+
let rb_handler = handler.rb_handler.into_value_with(&ruby);
|
|
491
481
|
|
|
492
482
|
// if `on_end_tag` function is defined, call it
|
|
493
483
|
if rb_handler.respond_to(Self::SELMA_ON_END_TAG, true).unwrap() {
|
|
494
|
-
// TODO: error here is an "EndTagError"
|
|
495
484
|
element
|
|
496
|
-
.
|
|
497
|
-
.unwrap()
|
|
498
|
-
.push(Box::new(move |end_tag| {
|
|
485
|
+
.on_end_tag(Box::new(move |end_tag| {
|
|
499
486
|
let (ref_wrap, anchor) = NativeRefWrap::wrap(end_tag);
|
|
500
487
|
|
|
501
488
|
let rb_end_tag = SelmaHTMLEndTag::new(ref_wrap);
|
|
@@ -509,7 +496,13 @@ impl SelmaRewriter {
|
|
|
509
496
|
Ok(_) => Ok(()),
|
|
510
497
|
Err(err) => Err(err.to_string().into()),
|
|
511
498
|
}
|
|
512
|
-
}))
|
|
499
|
+
}))
|
|
500
|
+
.map_err(|err| {
|
|
501
|
+
magnus::Error::new(
|
|
502
|
+
Ruby::get().unwrap().exception_runtime_error(),
|
|
503
|
+
err.to_string(),
|
|
504
|
+
)
|
|
505
|
+
})?;
|
|
513
506
|
}
|
|
514
507
|
|
|
515
508
|
let (ref_wrap, anchor) = NativeRefWrap::wrap(element);
|
|
@@ -521,7 +514,7 @@ impl SelmaRewriter {
|
|
|
521
514
|
match result {
|
|
522
515
|
Ok(_) => Ok(()),
|
|
523
516
|
Err(err) => Err(magnus::Error::new(
|
|
524
|
-
|
|
517
|
+
ruby.exception_runtime_error(),
|
|
525
518
|
format!("{err:?}"),
|
|
526
519
|
)),
|
|
527
520
|
}
|
|
@@ -531,7 +524,8 @@ impl SelmaRewriter {
|
|
|
531
524
|
handler: &Handler,
|
|
532
525
|
text_chunk: &mut TextChunk,
|
|
533
526
|
) -> Result<(), magnus::Error> {
|
|
534
|
-
let
|
|
527
|
+
let ruby = Ruby::get().unwrap();
|
|
528
|
+
let rb_handler = handler.rb_handler.into_value_with(&ruby);
|
|
535
529
|
|
|
536
530
|
// prevents missing `handle_text_chunk` function
|
|
537
531
|
let content = text_chunk.as_str();
|
|
@@ -553,32 +547,38 @@ impl SelmaRewriter {
|
|
|
553
547
|
match result {
|
|
554
548
|
Ok(_) => Ok(()),
|
|
555
549
|
Err(err) => Err(magnus::Error::new(
|
|
556
|
-
|
|
550
|
+
ruby.exception_runtime_error(),
|
|
557
551
|
format!("{err:?}"),
|
|
558
552
|
)),
|
|
559
553
|
}
|
|
560
554
|
}
|
|
561
555
|
|
|
562
556
|
fn get_memory_options(binding: &Ref<Rewriter>) -> MemorySettings {
|
|
563
|
-
let options = &binding.options
|
|
564
|
-
MemorySettings
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
557
|
+
let options = &binding.options;
|
|
558
|
+
MemorySettings::new()
|
|
559
|
+
.with_max_allowed_memory_usage(options.max_allowed_memory_usage)
|
|
560
|
+
.with_preallocated_parsing_buffer_size(options.preallocated_parsing_buffer_size)
|
|
568
561
|
}
|
|
569
562
|
}
|
|
570
563
|
|
|
571
564
|
impl RewriterOptions {
|
|
565
|
+
// Mirrors lol_html's `MemorySettings` defaults, which are no longer exposed as
|
|
566
|
+
// public fields as of lol_html 3.0 (settings are now consuming builders).
|
|
567
|
+
const DEFAULT_MAX_ALLOWED_MEMORY_USAGE: usize = usize::MAX;
|
|
568
|
+
const DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE: usize = 1024;
|
|
569
|
+
|
|
572
570
|
pub fn new() -> Self {
|
|
573
571
|
Self {
|
|
574
|
-
|
|
572
|
+
max_allowed_memory_usage: Self::DEFAULT_MAX_ALLOWED_MEMORY_USAGE,
|
|
573
|
+
preallocated_parsing_buffer_size: Self::DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE,
|
|
575
574
|
}
|
|
576
575
|
}
|
|
577
576
|
}
|
|
578
577
|
|
|
579
578
|
pub fn init(m_selma: RModule) -> Result<(), magnus::Error> {
|
|
579
|
+
let ruby = Ruby::get().unwrap();
|
|
580
580
|
let c_rewriter = m_selma
|
|
581
|
-
.define_class("Rewriter",
|
|
581
|
+
.define_class("Rewriter", ruby.class_object())
|
|
582
582
|
.expect("cannot define class Selma::Rewriter");
|
|
583
583
|
|
|
584
584
|
c_rewriter.define_singleton_method("new", function!(SelmaRewriter::new, -1))?;
|
data/ext/selma/src/sanitizer.rs
CHANGED
|
@@ -5,7 +5,7 @@ use lol_html::{
|
|
|
5
5
|
html_content::{Comment, ContentType, Doctype, Element, EndTag},
|
|
6
6
|
};
|
|
7
7
|
use magnus::{
|
|
8
|
-
|
|
8
|
+
eval, function, method,
|
|
9
9
|
r_hash::ForEach,
|
|
10
10
|
scan_args,
|
|
11
11
|
value::{Opaque, ReprValue},
|
|
@@ -125,22 +125,22 @@ impl SelmaSanitizer {
|
|
|
125
125
|
allowed_protocols.foreach(|element_name: String, protocols: RHash| {
|
|
126
126
|
protocols.foreach(|attribute_name: String, protocol_list: Value| {
|
|
127
127
|
let protocols: RArray;
|
|
128
|
-
if protocol_list.is_kind_of(
|
|
128
|
+
if protocol_list.is_kind_of(ruby.class_array()) {
|
|
129
129
|
protocols = RArray::from_value(protocol_list).unwrap();
|
|
130
130
|
if protocols.includes(ruby.to_symbol("all")) {
|
|
131
131
|
return Err(magnus::Error::new(
|
|
132
|
-
|
|
132
|
+
ruby.exception_arg_error(),
|
|
133
133
|
"`:all` must be passed outside of an array".to_string(),
|
|
134
134
|
));
|
|
135
135
|
}
|
|
136
|
-
} else if protocol_list.is_kind_of(
|
|
136
|
+
} else if protocol_list.is_kind_of(ruby.class_symbol())
|
|
137
137
|
&& Symbol::from_value(protocol_list) == eval(":all").unwrap()
|
|
138
138
|
{
|
|
139
|
-
protocols =
|
|
139
|
+
protocols = ruby.ary_new();
|
|
140
140
|
protocols.push(ruby.to_symbol("all"))?;
|
|
141
141
|
} else {
|
|
142
142
|
return Err(magnus::Error::new(
|
|
143
|
-
|
|
143
|
+
ruby.exception_arg_error(),
|
|
144
144
|
"Protocol list must be an array, or just `:all`".to_string(),
|
|
145
145
|
));
|
|
146
146
|
}
|
|
@@ -220,15 +220,15 @@ impl SelmaSanitizer {
|
|
|
220
220
|
// end
|
|
221
221
|
// end
|
|
222
222
|
if let Some(remove_contents) = config.get(ruby.to_symbol("remove_contents")) {
|
|
223
|
-
if remove_contents.is_kind_of(
|
|
224
|
-
|| remove_contents.is_kind_of(
|
|
223
|
+
if remove_contents.is_kind_of(ruby.class_true_class())
|
|
224
|
+
|| remove_contents.is_kind_of(ruby.class_false_class())
|
|
225
225
|
{
|
|
226
226
|
Self::set_all_flags(
|
|
227
227
|
flags,
|
|
228
228
|
Self::SELMA_SANITIZER_REMOVE_CONTENTS,
|
|
229
229
|
remove_contents.to_bool(),
|
|
230
230
|
);
|
|
231
|
-
} else if remove_contents.is_kind_of(
|
|
231
|
+
} else if remove_contents.is_kind_of(ruby.class_array()) {
|
|
232
232
|
let elements = RArray::from_value(remove_contents).unwrap();
|
|
233
233
|
elements
|
|
234
234
|
.into_iter()
|
|
@@ -245,7 +245,7 @@ impl SelmaSanitizer {
|
|
|
245
245
|
});
|
|
246
246
|
} else {
|
|
247
247
|
return Err(magnus::Error::new(
|
|
248
|
-
|
|
248
|
+
ruby.exception_arg_error(),
|
|
249
249
|
"remove_contents must be `true`, `false`, or an array".to_string(),
|
|
250
250
|
));
|
|
251
251
|
}
|
|
@@ -354,11 +354,12 @@ impl SelmaSanitizer {
|
|
|
354
354
|
attr_name: String,
|
|
355
355
|
allow_list: RArray,
|
|
356
356
|
) {
|
|
357
|
+
let ruby = Ruby::get().unwrap();
|
|
357
358
|
let protocol_sanitizers = &mut element_sanitizer.protocol_sanitizers.borrow_mut();
|
|
358
359
|
|
|
359
360
|
for allowed_protocol in allow_list.into_iter() {
|
|
360
361
|
let protocol_list = protocol_sanitizers.get_mut(&attr_name);
|
|
361
|
-
if allowed_protocol.is_kind_of(
|
|
362
|
+
if allowed_protocol.is_kind_of(ruby.class_string()) {
|
|
362
363
|
match protocol_list {
|
|
363
364
|
None => {
|
|
364
365
|
protocol_sanitizers
|
|
@@ -366,7 +367,7 @@ impl SelmaSanitizer {
|
|
|
366
367
|
}
|
|
367
368
|
Some(protocol_list) => protocol_list.push(allowed_protocol.to_string()),
|
|
368
369
|
}
|
|
369
|
-
} else if allowed_protocol.is_kind_of(
|
|
370
|
+
} else if allowed_protocol.is_kind_of(ruby.class_symbol()) {
|
|
370
371
|
let protocol_config = allowed_protocol.inspect();
|
|
371
372
|
if protocol_config == ":relative" {
|
|
372
373
|
match protocol_list {
|
|
@@ -685,13 +686,11 @@ impl SelmaSanitizer {
|
|
|
685
686
|
|
|
686
687
|
fn check_if_end_tag_needs_removal(element: &mut Element) {
|
|
687
688
|
if element.removed() && !crate::tags::Tag::tag_from_element(element).self_closing {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
Ok(())
|
|
694
|
-
}));
|
|
689
|
+
// ignore void elements (lol_html's void list may differ from selma's `self_closing`)
|
|
690
|
+
let _ = element.on_end_tag(Box::new(move |end| {
|
|
691
|
+
Self::remove_end_tag(end);
|
|
692
|
+
Ok(())
|
|
693
|
+
}));
|
|
695
694
|
}
|
|
696
695
|
}
|
|
697
696
|
|
|
@@ -710,8 +709,9 @@ impl SelmaSanitizer {
|
|
|
710
709
|
}
|
|
711
710
|
|
|
712
711
|
pub fn init(m_selma: RModule) -> Result<(), magnus::Error> {
|
|
712
|
+
let ruby = Ruby::get().unwrap();
|
|
713
713
|
let c_sanitizer = m_selma
|
|
714
|
-
.define_class("Sanitizer",
|
|
714
|
+
.define_class("Sanitizer", ruby.class_object())
|
|
715
715
|
.expect("cannot define class Selma::Sanitizer");
|
|
716
716
|
|
|
717
717
|
c_sanitizer.define_singleton_method("new", function!(SelmaSanitizer::new, -1))?;
|
data/ext/selma/src/selector.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use magnus::{
|
|
1
|
+
use magnus::{function, scan_args, Error, Module, Object, RModule, Ruby, Value};
|
|
2
2
|
|
|
3
3
|
#[derive(Clone, Debug)]
|
|
4
4
|
#[magnus::wrap(class = "Selma::Selector")]
|
|
@@ -14,31 +14,30 @@ impl SelmaSelector {
|
|
|
14
14
|
fn new(args: &[Value]) -> Result<Self, Error> {
|
|
15
15
|
let (match_element, match_text_within, rb_ignore_text_within) =
|
|
16
16
|
Self::scan_parse_args(args)?;
|
|
17
|
+
let ruby = Ruby::get().unwrap();
|
|
17
18
|
|
|
18
19
|
if match_element.is_none() && match_text_within.is_none() {
|
|
19
20
|
return Err(Error::new(
|
|
20
|
-
|
|
21
|
+
ruby.exception_arg_error(),
|
|
21
22
|
"Neither `match_element` nor `match_text_within` option given",
|
|
22
23
|
));
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// FIXME: not excited about this double parse work (`element!` does it too),
|
|
26
27
|
// but at least we can bail ASAP if the CSS is invalid
|
|
27
|
-
if
|
|
28
|
-
let css = match_element.as_ref().unwrap();
|
|
28
|
+
if let Some(css) = &match_element {
|
|
29
29
|
if css.parse::<lol_html::Selector>().is_err() {
|
|
30
30
|
return Err(Error::new(
|
|
31
|
-
|
|
31
|
+
ruby.exception_arg_error(),
|
|
32
32
|
format!("Could not parse `match_element` (`{css:?}`) as valid CSS"),
|
|
33
33
|
));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if
|
|
38
|
-
let css = match_text_within.as_ref().unwrap();
|
|
37
|
+
if let Some(css) = &match_text_within {
|
|
39
38
|
if css.parse::<lol_html::Selector>().is_err() {
|
|
40
39
|
return Err(Error::new(
|
|
41
|
-
|
|
40
|
+
ruby.exception_arg_error(),
|
|
42
41
|
format!("Could not parse `match_text_within` (`{css:?}`) as valid CSS",),
|
|
43
42
|
));
|
|
44
43
|
}
|
|
@@ -88,22 +87,23 @@ impl SelmaSelector {
|
|
|
88
87
|
Ok((match_element, match_text_within, rb_ignore_text_within))
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
pub fn match_element(&self) -> Option
|
|
92
|
-
self.match_element.
|
|
90
|
+
pub fn match_element(&self) -> Option<&str> {
|
|
91
|
+
self.match_element.as_deref()
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
pub fn match_text_within(&self) -> Option
|
|
96
|
-
self.match_text_within.
|
|
94
|
+
pub fn match_text_within(&self) -> Option<&str> {
|
|
95
|
+
self.match_text_within.as_deref()
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
pub fn ignore_text_within(&self) -> Option
|
|
100
|
-
self.ignore_text_within.
|
|
98
|
+
pub fn ignore_text_within(&self) -> Option<&[String]> {
|
|
99
|
+
self.ignore_text_within.as_deref()
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
pub fn init(m_selma: RModule) -> Result<(), Error> {
|
|
104
|
+
let ruby = Ruby::get().unwrap();
|
|
105
105
|
let c_selector = m_selma
|
|
106
|
-
.define_class("Selector",
|
|
106
|
+
.define_class("Selector", ruby.class_object())
|
|
107
107
|
.expect("cannot define class Selma::Selector");
|
|
108
108
|
|
|
109
109
|
c_selector.define_singleton_method("new", function!(SelmaSelector::new, -1))?;
|
data/lib/selma/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: selma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Garen J. Torikian
|
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
113
|
version: '3.4'
|
|
114
114
|
requirements: []
|
|
115
|
-
rubygems_version: 4.0.
|
|
115
|
+
rubygems_version: 4.0.10
|
|
116
116
|
specification_version: 4
|
|
117
117
|
summary: Selma selects and matches HTML nodes using CSS rules. Backed by Rust's lol_html
|
|
118
118
|
parser.
|