@@ -21,7 +21,7 @@ impl<'a> MarkdownRenderer<'a> {
21
21
///
22
22
/// Per `readme_to_html`, `base_url` is the base URL prepended to any
23
23
/// relative links in the input document. See that function for more detail.
24
- fn new ( base_url : Option < & ' a str > ) -> MarkdownRenderer < ' a > {
24
+ fn new ( base_url : Option < & ' a str > , base_dir : & ' a str ) -> MarkdownRenderer < ' a > {
25
25
let allowed_classes = hashmap ( & [ (
26
26
"code" ,
27
27
hashset ( & [
@@ -42,7 +42,7 @@ impl<'a> MarkdownRenderer<'a> {
42
42
"language-yaml" ,
43
43
] ) ,
44
44
) ] ) ;
45
- let sanitize_url = UrlRelative :: Custom ( Box :: new ( SanitizeUrl :: new ( base_url) ) ) ;
45
+ let sanitize_url = UrlRelative :: Custom ( Box :: new ( SanitizeUrl :: new ( base_url, base_dir ) ) ) ;
46
46
47
47
let mut html_sanitizer = Builder :: default ( ) ;
48
48
html_sanitizer
@@ -131,10 +131,11 @@ fn canon_base_url(mut base_url: String) -> String {
131
131
/// Sanitize relative URLs in README files.
132
132
struct SanitizeUrl {
133
133
base_url : Option < String > ,
134
+ base_dir : String ,
134
135
}
135
136
136
137
impl SanitizeUrl {
137
- fn new ( base_url : Option < & str > ) -> Self {
138
+ fn new ( base_url : Option < & str > , base_dir : & str ) -> Self {
138
139
let base_url = base_url
139
140
. and_then ( |base_url| Url :: parse ( base_url) . ok ( ) )
140
141
. and_then ( |url| match url. host_str ( ) {
@@ -143,7 +144,10 @@ impl SanitizeUrl {
143
144
}
144
145
_ => None ,
145
146
} ) ;
146
- Self { base_url }
147
+ Self {
148
+ base_url,
149
+ base_dir : base_dir. to_owned ( ) ,
150
+ }
147
151
}
148
152
}
149
153
@@ -197,6 +201,10 @@ impl UrlRelativeEvaluate for SanitizeUrl {
197
201
add_sanitize_query,
198
202
} = is_media_url ( url) ;
199
203
new_url += if is_media { "raw/HEAD" } else { "blob/HEAD" } ;
204
+ if !self . base_dir . is_empty ( ) {
205
+ new_url += "/" ;
206
+ new_url += & self . base_dir ;
207
+ }
200
208
if !url. starts_with ( '/' ) {
201
209
new_url. push ( '/' ) ;
202
210
}
@@ -214,22 +222,15 @@ impl UrlRelativeEvaluate for SanitizeUrl {
214
222
215
223
/// Renders Markdown text to sanitized HTML with a given `base_url`.
216
224
/// See `readme_to_html` for the interpretation of `base_url`.
217
- fn markdown_to_html ( text : & str , base_url : Option < & str > ) -> String {
218
- let renderer = MarkdownRenderer :: new ( base_url) ;
225
+ fn markdown_to_html ( text : & str , base_url : Option < & str > , base_dir : & str ) -> String {
226
+ let renderer = MarkdownRenderer :: new ( base_url, base_dir ) ;
219
227
renderer. to_html ( text)
220
228
}
221
229
222
230
/// Any readme with a filename ending in one of these extensions will be rendered as Markdown.
223
231
/// Note we also render a readme as Markdown if _no_ extension is on the filename.
224
- static MARKDOWN_EXTENSIONS : [ & str ; 7 ] = [
225
- ".md" ,
226
- ".markdown" ,
227
- ".mdown" ,
228
- ".mdwn" ,
229
- ".mkd" ,
230
- ".mkdn" ,
231
- ".mkdown" ,
232
- ] ;
232
+ static MARKDOWN_EXTENSIONS : [ & str ; 7 ] =
233
+ [ "md" , "markdown" , "mdown" , "mdwn" , "mkd" , "mkdn" , "mkdown" ] ;
233
234
234
235
/// Renders a readme to sanitized HTML. An appropriate rendering method is chosen depending
235
236
/// on the extension of the supplied `filename`.
@@ -250,11 +251,18 @@ static MARKDOWN_EXTENSIONS: [&str; 7] = [
250
251
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
251
252
/// let rendered = readme_to_html(text, "README.md", None)?;
252
253
/// ```
253
- pub fn readme_to_html ( text : & str , filename : & str , base_url : Option < & str > ) -> String {
254
- let filename = filename. to_lowercase ( ) ;
254
+ pub fn readme_to_html ( text : & str , readme_path : & str , base_url : Option < & str > ) -> String {
255
+ let readme_path = Path :: new ( readme_path) ;
256
+ let readme_dir = readme_path. parent ( ) . and_then ( |p| p. to_str ( ) ) . unwrap_or ( "" ) ;
255
257
256
- if !filename. contains ( '.' ) || MARKDOWN_EXTENSIONS . iter ( ) . any ( |e| filename. ends_with ( e) ) {
257
- return markdown_to_html ( text, base_url) ;
258
+ if readme_path. extension ( ) . is_none ( ) {
259
+ return markdown_to_html ( text, base_url, readme_dir) ;
260
+ }
261
+
262
+ if let Some ( ext) = readme_path. extension ( ) . and_then ( |ext| ext. to_str ( ) ) {
263
+ if MARKDOWN_EXTENSIONS . contains ( & ext. to_lowercase ( ) . as_str ( ) ) {
264
+ return markdown_to_html ( text, base_url, readme_dir) ;
265
+ }
258
266
}
259
267
260
268
encode_minimal ( text) . replace ( "\n " , "<br>\n " )
@@ -266,13 +274,13 @@ pub fn render_and_upload_readme(
266
274
env : & Environment ,
267
275
version_id : i32 ,
268
276
text : String ,
269
- file_name : String ,
277
+ readme_path : String ,
270
278
base_url : Option < String > ,
271
279
) -> Result < ( ) , PerformError > {
272
280
use crate :: schema:: * ;
273
281
use diesel:: prelude:: * ;
274
282
275
- let rendered = readme_to_html ( & text, & file_name , base_url. as_deref ( ) ) ;
283
+ let rendered = readme_to_html ( & text, & readme_path , base_url. as_deref ( ) ) ;
276
284
277
285
conn. transaction ( || {
278
286
Version :: record_readme_rendering ( version_id, conn) ?;
@@ -311,14 +319,14 @@ mod tests {
311
319
#[ test]
312
320
fn empty_text ( ) {
313
321
let text = "" ;
314
- let result = markdown_to_html ( text, None ) ;
322
+ let result = markdown_to_html ( text, None , "" ) ;
315
323
assert_eq ! ( result, "" ) ;
316
324
}
317
325
318
326
#[ test]
319
327
fn text_with_script_tag ( ) {
320
328
let text = "foo_readme\n \n <script>alert('Hello World')</script>" ;
321
- let result = markdown_to_html ( text, None ) ;
329
+ let result = markdown_to_html ( text, None , "" ) ;
322
330
assert_eq ! (
323
331
result,
324
332
"<p>foo_readme</p>\n <script>alert(\' Hello World\' )</script>\n "
@@ -328,7 +336,7 @@ mod tests {
328
336
#[ test]
329
337
fn text_with_iframe_tag ( ) {
330
338
let text = "foo_readme\n \n <iframe>alert('Hello World')</iframe>" ;
331
- let result = markdown_to_html ( text, None ) ;
339
+ let result = markdown_to_html ( text, None , "" ) ;
332
340
assert_eq ! (
333
341
result,
334
342
"<p>foo_readme</p>\n <iframe>alert(\' Hello World\' )</iframe>\n "
@@ -338,14 +346,14 @@ mod tests {
338
346
#[ test]
339
347
fn text_with_unknown_tag ( ) {
340
348
let text = "foo_readme\n \n <unknown>alert('Hello World')</unknown>" ;
341
- let result = markdown_to_html ( text, None ) ;
349
+ let result = markdown_to_html ( text, None , "" ) ;
342
350
assert_eq ! ( result, "<p>foo_readme</p>\n <p>alert(\' Hello World\' )</p>\n " ) ;
343
351
}
344
352
345
353
#[ test]
346
354
fn text_with_inline_javascript ( ) {
347
355
let text = r#"foo_readme\n\n<a href="https://crates.io/crates/cargo-registry" onclick="window.alert('Got you')">Crate page</a>"# ;
348
- let result = markdown_to_html ( text, None ) ;
356
+ let result = markdown_to_html ( text, None , "" ) ;
349
357
assert_eq ! (
350
358
result,
351
359
"<p>foo_readme\\ n\\ n<a href=\" https://crates.io/crates/cargo-registry\" rel=\" nofollow noopener noreferrer\" >Crate page</a></p>\n "
@@ -357,7 +365,7 @@ mod tests {
357
365
#[ test]
358
366
fn text_with_fancy_single_quotes ( ) {
359
367
let text = "wb’" ;
360
- let result = markdown_to_html ( text, None ) ;
368
+ let result = markdown_to_html ( text, None , "" ) ;
361
369
assert_eq ! ( result, "<p>wb’</p>\n " ) ;
362
370
}
363
371
@@ -366,7 +374,7 @@ mod tests {
366
374
let code_block = r#"```rust \
367
375
println!("Hello World"); \
368
376
```"# ;
369
- let result = markdown_to_html ( code_block, None ) ;
377
+ let result = markdown_to_html ( code_block, None , "" ) ;
370
378
assert ! ( result. contains( "<code class=\" language-rust\" >" ) ) ;
371
379
}
372
380
@@ -375,14 +383,14 @@ mod tests {
375
383
let code_block = r#"```rust , no_run \
376
384
println!("Hello World"); \
377
385
```"# ;
378
- let result = markdown_to_html ( code_block, None ) ;
386
+ let result = markdown_to_html ( code_block, None , "" ) ;
379
387
assert ! ( result. contains( "<code class=\" language-rust\" >" ) ) ;
380
388
}
381
389
382
390
#[ test]
383
391
fn text_with_forbidden_class_attribute ( ) {
384
392
let text = "<p class='bad-class'>Hello World!</p>" ;
385
- let result = markdown_to_html ( text, None ) ;
393
+ let result = markdown_to_html ( text, None , "" ) ;
386
394
assert_eq ! ( result, "<p>Hello World!</p>\n " ) ;
387
395
}
388
396
@@ -403,7 +411,7 @@ mod tests {
403
411
if extra_slash { "/" } else { "" } ,
404
412
) ;
405
413
406
- let result = markdown_to_html ( absolute, Some ( & url) ) ;
414
+ let result = markdown_to_html ( absolute, Some ( & url) , "" ) ;
407
415
assert_eq ! (
408
416
result,
409
417
format!(
@@ -412,7 +420,7 @@ mod tests {
412
420
)
413
421
) ;
414
422
415
- let result = markdown_to_html ( relative, Some ( & url) ) ;
423
+ let result = markdown_to_html ( relative, Some ( & url) , "" ) ;
416
424
assert_eq ! (
417
425
result,
418
426
format!(
@@ -421,7 +429,7 @@ mod tests {
421
429
)
422
430
) ;
423
431
424
- let result = markdown_to_html ( image, Some ( & url) ) ;
432
+ let result = markdown_to_html ( image, Some ( & url) , "" ) ;
425
433
assert_eq ! (
426
434
result,
427
435
format!(
@@ -430,7 +438,7 @@ mod tests {
430
438
)
431
439
) ;
432
440
433
- let result = markdown_to_html ( html_image, Some ( & url) ) ;
441
+ let result = markdown_to_html ( html_image, Some ( & url) , "" ) ;
434
442
assert_eq ! (
435
443
result,
436
444
format!(
@@ -439,18 +447,36 @@ mod tests {
439
447
)
440
448
) ;
441
449
442
- let result = markdown_to_html ( svg, Some ( & url) ) ;
450
+ let result = markdown_to_html ( svg, Some ( & url) , "" ) ;
443
451
assert_eq ! (
444
452
result,
445
453
format!(
446
454
"<p><img src=\" https://{}/rust-lang/test/raw/HEAD/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
447
455
host
448
456
)
449
457
) ;
458
+
459
+ let result = markdown_to_html ( svg, Some ( & url) , "subdir" ) ;
460
+ assert_eq ! (
461
+ result,
462
+ format!(
463
+ "<p><img src=\" https://{}/rust-lang/test/raw/HEAD/subdir/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
464
+ host
465
+ )
466
+ ) ;
467
+
468
+ let result = markdown_to_html ( svg, Some ( & url) , "subdir1/subdir2" ) ;
469
+ assert_eq ! (
470
+ result,
471
+ format!(
472
+ "<p><img src=\" https://{}/rust-lang/test/raw/HEAD/subdir1/subdir2/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
473
+ host
474
+ )
475
+ ) ;
450
476
}
451
477
}
452
478
453
- let result = markdown_to_html ( absolute, Some ( "https://google.com/" ) ) ;
479
+ let result = markdown_to_html ( absolute, Some ( "https://google.com/" ) , "" ) ;
454
480
assert_eq ! (
455
481
result,
456
482
"<p><a rel=\" nofollow noopener noreferrer\" >hi</a></p>\n "
@@ -462,7 +488,7 @@ mod tests {
462
488
let readme_text =
463
489
"[](https://crates.io/crates/clap)" ;
464
490
let repository = "https://github.com/kbknapp/clap-rs/" ;
465
- let result = markdown_to_html ( readme_text, Some ( repository) ) ;
491
+ let result = markdown_to_html ( readme_text, Some ( repository) , "" ) ;
466
492
467
493
assert_eq ! (
468
494
result,
@@ -472,12 +498,32 @@ mod tests {
472
498
473
499
#[ test]
474
500
fn readme_to_html_renders_markdown ( ) {
475
- for f in & [ "README" , "readme.md" , "README.MARKDOWN" , "whatever.mkd" ] {
501
+ for f in & [
502
+ "README" ,
503
+ "readme.md" ,
504
+ "README.MARKDOWN" ,
505
+ "whatever.mkd" ,
506
+ "s/readme.md" ,
507
+ "s1/s2/readme.md" ,
508
+ ] {
476
509
assert_eq ! (
477
510
readme_to_html( "*lobster*" , f, None ) ,
478
511
"<p><em>lobster</em></p>\n "
479
512
) ;
480
513
}
514
+
515
+ assert_eq ! (
516
+ readme_to_html( "*[lobster](docs/lobster)*" , "readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
517
+ "<p><em><a href=\" https://github.com/rust-lang/test/blob/HEAD/docs/lobster\" rel=\" nofollow noopener noreferrer\" >lobster</a></em></p>\n "
518
+ ) ;
519
+ assert_eq ! (
520
+ readme_to_html( "*[lobster](docs/lobster)*" , "s/readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
521
+ "<p><em><a href=\" https://github.com/rust-lang/test/blob/HEAD/s/docs/lobster\" rel=\" nofollow noopener noreferrer\" >lobster</a></em></p>\n "
522
+ ) ;
523
+ assert_eq ! (
524
+ readme_to_html( "*[lobster](docs/lobster)*" , "s1/s2/readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
525
+ "<p><em><a href=\" https://github.com/rust-lang/test/blob/HEAD/s1/s2/docs/lobster\" rel=\" nofollow noopener noreferrer\" >lobster</a></em></p>\n "
526
+ ) ;
481
527
}
482
528
483
529
#[ test]
@@ -493,7 +539,7 @@ mod tests {
493
539
#[ test]
494
540
fn header_has_tags ( ) {
495
541
let text = "# My crate\n \n Hello, world!\n " ;
496
- let result = markdown_to_html ( text, None ) ;
542
+ let result = markdown_to_html ( text, None , "" ) ;
497
543
assert_eq ! (
498
544
result,
499
545
"<h1><a href=\" #my-crate\" id=\" user-content-my-crate\" rel=\" nofollow noopener noreferrer\" ></a>My crate</h1>\n <p>Hello, world!</p>\n "
@@ -504,7 +550,7 @@ mod tests {
504
550
fn manual_anchor_is_sanitized ( ) {
505
551
let text =
506
552
"<h1><a href=\" #my-crate\" id=\" my-crate\" ></a>My crate</h1>\n <p>Hello, world!</p>\n " ;
507
- let result = markdown_to_html ( text, None ) ;
553
+ let result = markdown_to_html ( text, None , "" ) ;
508
554
assert_eq ! (
509
555
result,
510
556
"<h1><a href=\" #my-crate\" id=\" user-content-my-crate\" rel=\" nofollow noopener noreferrer\" ></a>My crate</h1>\n <p>Hello, world!</p>\n "
@@ -514,7 +560,7 @@ mod tests {
514
560
#[ test]
515
561
fn tables_with_rowspan_and_colspan ( ) {
516
562
let text = "<table><tr><th rowspan=\" 1\" colspan=\" 2\" >Target</th></tr></table>\n " ;
517
- let result = markdown_to_html ( text, None ) ;
563
+ let result = markdown_to_html ( text, None , "" ) ;
518
564
assert_eq ! (
519
565
result,
520
566
"<table><tbody><tr><th rowspan=\" 1\" colspan=\" 2\" >Target</th></tr></tbody></table>\n "
@@ -524,7 +570,7 @@ mod tests {
524
570
#[ test]
525
571
fn text_alignment ( ) {
526
572
let text = "<h1 align=\" center\" >foo-bar</h1>\n <h5 align=\" center\" >Hello World!</h5>\n " ;
527
- let result = markdown_to_html ( text, None ) ;
573
+ let result = markdown_to_html ( text, None , "" ) ;
528
574
assert_eq ! (
529
575
result,
530
576
"<h1 align=\" center\" >foo-bar</h1>\n <h5 align=\" center\" >Hello World!</h5>\n "
@@ -535,7 +581,7 @@ mod tests {
535
581
fn image_alignment ( ) {
536
582
let text =
537
583
"<p align=\" center\" ><img src=\" https://img.shields.io/crates/v/clap.svg\" alt=\" \" ></p>\n " ;
538
- let result = markdown_to_html ( text, None ) ;
584
+ let result = markdown_to_html ( text, None , "" ) ;
539
585
assert_eq ! (
540
586
result,
541
587
"<p align=\" center\" ><img src=\" https://img.shields.io/crates/v/clap.svg\" alt=\" \" ></p>\n "
0 commit comments