@@ -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
@@ -402,7 +410,7 @@ mod tests {
402
410
if extra_slash { "/" } else { "" } ,
403
411
) ;
404
412
405
- let result = markdown_to_html ( absolute, Some ( & url) ) ;
413
+ let result = markdown_to_html ( absolute, Some ( & url) , "" ) ;
406
414
assert_eq ! (
407
415
result,
408
416
format!(
@@ -411,7 +419,7 @@ mod tests {
411
419
)
412
420
) ;
413
421
414
- let result = markdown_to_html ( relative, Some ( & url) ) ;
422
+ let result = markdown_to_html ( relative, Some ( & url) , "" ) ;
415
423
assert_eq ! (
416
424
result,
417
425
format!(
@@ -420,7 +428,7 @@ mod tests {
420
428
)
421
429
) ;
422
430
423
- let result = markdown_to_html ( image, Some ( & url) ) ;
431
+ let result = markdown_to_html ( image, Some ( & url) , "" ) ;
424
432
assert_eq ! (
425
433
result,
426
434
format!(
@@ -429,18 +437,36 @@ mod tests {
429
437
)
430
438
) ;
431
439
432
- let result = markdown_to_html ( svg, Some ( & url) ) ;
440
+ let result = markdown_to_html ( svg, Some ( & url) , "" ) ;
433
441
assert_eq ! (
434
442
result,
435
443
format!(
436
444
"<p><img src=\" https://{}/rust-lang/test/raw/HEAD/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
437
445
host
438
446
)
439
447
) ;
448
+
449
+ let result = markdown_to_html ( svg, Some ( & url) , "subdir" ) ;
450
+ assert_eq ! (
451
+ result,
452
+ format!(
453
+ "<p><img src=\" https://{}/rust-lang/test/raw/HEAD/subdir/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
454
+ host
455
+ )
456
+ ) ;
457
+
458
+ let result = markdown_to_html ( svg, Some ( & url) , "subdir1/subdir2" ) ;
459
+ assert_eq ! (
460
+ result,
461
+ format!(
462
+ "<p><img src=\" https://{}/rust-lang/test/raw/HEAD/subdir1/subdir2/sanitize.svg?sanitize=true\" alt=\" alt\" ></p>\n " ,
463
+ host
464
+ )
465
+ ) ;
440
466
}
441
467
}
442
468
443
- let result = markdown_to_html ( absolute, Some ( "https://google.com/" ) ) ;
469
+ let result = markdown_to_html ( absolute, Some ( "https://google.com/" ) , "" ) ;
444
470
assert_eq ! (
445
471
result,
446
472
"<p><a rel=\" nofollow noopener noreferrer\" >hi</a></p>\n "
@@ -452,7 +478,7 @@ mod tests {
452
478
let readme_text =
453
479
"[](https://crates.io/crates/clap)" ;
454
480
let repository = "https://github.com/kbknapp/clap-rs/" ;
455
- let result = markdown_to_html ( readme_text, Some ( repository) ) ;
481
+ let result = markdown_to_html ( readme_text, Some ( repository) , "" ) ;
456
482
457
483
assert_eq ! (
458
484
result,
@@ -462,12 +488,32 @@ mod tests {
462
488
463
489
#[ test]
464
490
fn readme_to_html_renders_markdown ( ) {
465
- for f in & [ "README" , "readme.md" , "README.MARKDOWN" , "whatever.mkd" ] {
491
+ for f in & [
492
+ "README" ,
493
+ "readme.md" ,
494
+ "README.MARKDOWN" ,
495
+ "whatever.mkd" ,
496
+ "s/readme.md" ,
497
+ "s1/s2/readme.md" ,
498
+ ] {
466
499
assert_eq ! (
467
500
readme_to_html( "*lobster*" , f, None ) ,
468
501
"<p><em>lobster</em></p>\n "
469
502
) ;
470
503
}
504
+
505
+ assert_eq ! (
506
+ readme_to_html( "*[lobster](docs/lobster)*" , "readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
507
+ "<p><em><a href=\" https://github.com/rust-lang/test/blob/HEAD/docs/lobster\" rel=\" nofollow noopener noreferrer\" >lobster</a></em></p>\n "
508
+ ) ;
509
+ assert_eq ! (
510
+ readme_to_html( "*[lobster](docs/lobster)*" , "s/readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
511
+ "<p><em><a href=\" https://github.com/rust-lang/test/blob/HEAD/s/docs/lobster\" rel=\" nofollow noopener noreferrer\" >lobster</a></em></p>\n "
512
+ ) ;
513
+ assert_eq ! (
514
+ readme_to_html( "*[lobster](docs/lobster)*" , "s1/s2/readme.md" , Some ( "https://github.com/rust-lang/test" ) ) ,
515
+ "<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 "
516
+ ) ;
471
517
}
472
518
473
519
#[ test]
@@ -483,7 +529,7 @@ mod tests {
483
529
#[ test]
484
530
fn header_has_tags ( ) {
485
531
let text = "# My crate\n \n Hello, world!\n " ;
486
- let result = markdown_to_html ( text, None ) ;
532
+ let result = markdown_to_html ( text, None , "" ) ;
487
533
assert_eq ! (
488
534
result,
489
535
"<h1><a href=\" #my-crate\" id=\" user-content-my-crate\" rel=\" nofollow noopener noreferrer\" ></a>My crate</h1>\n <p>Hello, world!</p>\n "
@@ -494,7 +540,7 @@ mod tests {
494
540
fn manual_anchor_is_sanitized ( ) {
495
541
let text =
496
542
"<h1><a href=\" #my-crate\" id=\" my-crate\" ></a>My crate</h1>\n <p>Hello, world!</p>\n " ;
497
- let result = markdown_to_html ( text, None ) ;
543
+ let result = markdown_to_html ( text, None , "" ) ;
498
544
assert_eq ! (
499
545
result,
500
546
"<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
#[ test]
505
551
fn tables_with_rowspan_and_colspan ( ) {
506
552
let text = "<table><tr><th rowspan=\" 1\" colspan=\" 2\" >Target</th></tr></table>\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
"<table><tbody><tr><th rowspan=\" 1\" colspan=\" 2\" >Target</th></tr></tbody></table>\n "
@@ -514,7 +560,7 @@ mod tests {
514
560
#[ test]
515
561
fn text_alignment ( ) {
516
562
let text = "<h1 align=\" center\" >foo-bar</h1>\n <h5 align=\" center\" >Hello World!</h5>\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
"<h1 align=\" center\" >foo-bar</h1>\n <h5 align=\" center\" >Hello World!</h5>\n "
0 commit comments