markdown_convert 2.0.8__tar.gz → 2.0.10__tar.gz

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.
Files changed (18) hide show
  1. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/PKG-INFO +18 -6
  2. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/README.md +17 -5
  3. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/constants.py +1 -1
  4. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/convert.py +104 -6
  5. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/extras.py +28 -3
  6. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/resources.py +15 -3
  7. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/pyproject.toml +1 -1
  8. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/.gitignore +0 -0
  9. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/LICENSE +0 -0
  10. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/__init__.py +0 -0
  11. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/__main__.py +0 -0
  12. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/code.css +0 -0
  13. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/default.css +0 -0
  14. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/__init__.py +0 -0
  15. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/autoinstall.py +0 -0
  16. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/transform.py +0 -0
  17. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/utils.py +0 -0
  18. {markdown_convert-2.0.8 → markdown_convert-2.0.10}/markdown_convert/modules/validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown_convert
3
- Version: 2.0.8
3
+ Version: 2.0.10
4
4
  Summary: Convert Markdown files to PDF from your command line.
5
5
  Project-URL: homepage, https://github.com/Julynx/markdown_convert
6
6
  Author-email: Julio Cabria <juliocabria@tutanota.com>
@@ -67,7 +67,7 @@ Unlike other similar tools, `markdown-convert`:
67
67
  | **LaTeX Math Equations** | `$E=mc^2$` is rendered as a math equation. |
68
68
  | **Mermaid, Vega-Lite and Schemdraw Diagrams** | ` ```mermaid ...` is rendered as a diagram. |
69
69
  | **Syntax-Highlighted Code Blocks** | ` ```python ...` gets syntax highlighting. |
70
- | **Admonitions** | `!!! note ...` is styled as a note box. |
70
+ | **Admonitions** | `!!! note ...` is styled as a note box. |
71
71
  | **Dynamic Table of Contents** | `[TOC]` inserts a Table of Contents. |
72
72
  | **Image Attributes** | `![::shadow::](sky.png)` shows a shadow behind the image. |
73
73
  | **Captions** | `![sky](sky.png)_A beautiful sky_` shows a caption, centered below the image. |
@@ -112,11 +112,23 @@ Options:
112
112
  admonitions,anchors,task-lists,math,custom-spans,highlights,
113
113
  syntax-highlighting,table-of-contents,vega-lite,inline-math,
114
114
  block-math,schemdraw,mermaid,dynamic-tables,dynamic-queries
115
- --security=default|strict
115
+ --security=basic|default|strict
116
116
  Specify the security level.
117
- Strict mode disables inline HTML, internet access and JS,
118
- but local files can still be referenced.
119
- This improves security, but will break some extras.
117
+ Basic:
118
+ - Inline HTML: No restrictions.
119
+ - JS: Restricted to extras.
120
+ - Remote files: Restricted to images and fonts via https.
121
+ - Local files: No restrictions.
122
+ Default:
123
+ - Inline HTML: Disabled.
124
+ - JS: Restricted to extras.
125
+ - Remote files: Restricted to images and fonts via https.
126
+ - Local files: Restricted to files inside the base directory.
127
+ Strict:
128
+ - Inline HTML: Disabled.
129
+ - JS: Disabled (may break some extras).
130
+ - Remote files: Disabled (internet access completely disabled).
131
+ - Local files: Restricted to files inside the base directory.
120
132
  ```
121
133
 
122
134
  For example: `markdown-convert README.md --mode=live --css=style.css --out=output.pdf` will convert `README.md` to `output.pdf` using `style.css` and update the PDF live as you edit the Markdown file.
@@ -38,7 +38,7 @@ Unlike other similar tools, `markdown-convert`:
38
38
  | **LaTeX Math Equations** | `$E=mc^2$` is rendered as a math equation. |
39
39
  | **Mermaid, Vega-Lite and Schemdraw Diagrams** | ` ```mermaid ...` is rendered as a diagram. |
40
40
  | **Syntax-Highlighted Code Blocks** | ` ```python ...` gets syntax highlighting. |
41
- | **Admonitions** | `!!! note ...` is styled as a note box. |
41
+ | **Admonitions** | `!!! note ...` is styled as a note box. |
42
42
  | **Dynamic Table of Contents** | `[TOC]` inserts a Table of Contents. |
43
43
  | **Image Attributes** | `![::shadow::](sky.png)` shows a shadow behind the image. |
44
44
  | **Captions** | `![sky](sky.png)_A beautiful sky_` shows a caption, centered below the image. |
@@ -83,11 +83,23 @@ Options:
83
83
  admonitions,anchors,task-lists,math,custom-spans,highlights,
84
84
  syntax-highlighting,table-of-contents,vega-lite,inline-math,
85
85
  block-math,schemdraw,mermaid,dynamic-tables,dynamic-queries
86
- --security=default|strict
86
+ --security=basic|default|strict
87
87
  Specify the security level.
88
- Strict mode disables inline HTML, internet access and JS,
89
- but local files can still be referenced.
90
- This improves security, but will break some extras.
88
+ Basic:
89
+ - Inline HTML: No restrictions.
90
+ - JS: Restricted to extras.
91
+ - Remote files: Restricted to images and fonts via https.
92
+ - Local files: No restrictions.
93
+ Default:
94
+ - Inline HTML: Disabled.
95
+ - JS: Restricted to extras.
96
+ - Remote files: Restricted to images and fonts via https.
97
+ - Local files: Restricted to files inside the base directory.
98
+ Strict:
99
+ - Inline HTML: Disabled.
100
+ - JS: Disabled (may break some extras).
101
+ - Remote files: Disabled (internet access completely disabled).
102
+ - Local files: Restricted to files inside the base directory.
91
103
  ```
92
104
 
93
105
  For example: `markdown-convert README.md --mode=live --css=style.css --out=output.pdf` will convert `README.md` to `output.pdf` using `style.css` and update the PDF live as you edit the Markdown file.
@@ -42,7 +42,7 @@ OPTIONS = (
42
42
 
43
43
  OPTIONS_MODES = ("once", "live", "debug")
44
44
 
45
- OPTIONS_SECURITY = ("default", "strict")
45
+ OPTIONS_SECURITY = ("basic", "default", "strict")
46
46
 
47
47
  EXTRAS = {
48
48
  "admonitions": {
@@ -6,8 +6,10 @@ Author: @julynx
6
6
  import os
7
7
  import secrets
8
8
  import time
9
+ import urllib.request
9
10
  from datetime import datetime
10
11
  from pathlib import Path
12
+ from urllib.parse import urlparse
11
13
 
12
14
  from markdown_it import MarkdownIt
13
15
  from playwright.sync_api import sync_playwright
@@ -50,7 +52,21 @@ def convert(
50
52
  dump_html (bool=False): Dump the intermediate HTML to a file.
51
53
  extras (list=None): List of extras to use.
52
54
  security_level (str=None): Security level to use.
53
- "strict" disables JS, inline HTML and internet access.
55
+ Basic:
56
+ - Inline HTML: No restrictions.
57
+ - JS: Restricted to extras.
58
+ - Remote files: Restricted to images and fonts via https.
59
+ - Local files: No restrictions.
60
+ Default:
61
+ - Inline HTML: Disabled.
62
+ - JS: Restricted to extras.
63
+ - Remote files: Restricted to images and fonts via https.
64
+ - Local files: Restricted to files inside the base directory.
65
+ Strict:
66
+ - Inline HTML: Disabled.
67
+ - JS: Disabled (may break some extras).
68
+ - Remote files: Disabled (internet access completely disabled).
69
+ - Local files: Restricted to files inside the base directory.
54
70
  """
55
71
  markdown_text = (
56
72
  Path(markdown_path).read_text(encoding="utf-8")
@@ -97,7 +113,21 @@ def live_convert(
97
113
  extend_default_css (bool=True): Extend the default CSS file.
98
114
  extras (list=None): List of extras to use.
99
115
  security_level (str=None): Security level to use.
100
- "strict" disables JS, inline HTML and internet access.
116
+ Basic:
117
+ - Inline HTML: No restrictions.
118
+ - JS: Restricted to extras.
119
+ - Remote files: Restricted to images and fonts via https.
120
+ - Local files: No restrictions.
121
+ Default:
122
+ - Inline HTML: Disabled.
123
+ - JS: Restricted to extras.
124
+ - Remote files: Restricted to images and fonts via https.
125
+ - Local files: Restricted to files inside the base directory.
126
+ Strict:
127
+ - Inline HTML: Disabled.
128
+ - JS: Disabled (may break some extras).
129
+ - Remote files: Disabled (internet access completely disabled).
130
+ - Local files: Restricted to files inside the base directory.
101
131
  """
102
132
  if css_path is None:
103
133
  css_path = get_css_path()
@@ -140,7 +170,21 @@ def convert_text(
140
170
  dump_html (bool=False): Dump the intermediate HTML to a file.
141
171
  extras (list=None): List of extras to use.
142
172
  security_level (str=None): Security level to use.
143
- "strict" disables JS, inline HTML and internet access.
173
+ Basic:
174
+ - Inline HTML: No restrictions.
175
+ - JS: Restricted to extras.
176
+ - Remote files: Restricted to images and fonts via https.
177
+ - Local files: No restrictions.
178
+ Default:
179
+ - Inline HTML: Disabled.
180
+ - JS: Restricted to extras.
181
+ - Remote files: Restricted to images and fonts via https.
182
+ - Local files: Restricted to files inside the base directory.
183
+ Strict:
184
+ - Inline HTML: Disabled.
185
+ - JS: Disabled (may break some extras).
186
+ - Remote files: Disabled (internet access completely disabled).
187
+ - Local files: Restricted to files inside the base directory.
144
188
 
145
189
  Returns:
146
190
  PDF file as bytes if output_path is None, else None.
@@ -165,7 +209,7 @@ def convert_text(
165
209
  "js-default",
166
210
  {
167
211
  "breaks": True,
168
- "html": security_level != "strict",
212
+ "html": security_level == "basic",
169
213
  },
170
214
  )
171
215
  for extra in extras["markdown_it_extras"]:
@@ -225,7 +269,21 @@ class LiveConverter:
225
269
  extend_default_css (bool): Extend the default CSS file.
226
270
  extras (list=None): List of extras to use.
227
271
  security_level (str=None): Security level to use.
228
- "strict" disables JS, inline HTML and internet access.
272
+ Basic:
273
+ - Inline HTML: No restrictions.
274
+ - JS: Restricted to extras.
275
+ - Remote files: Restricted to images and fonts via https.
276
+ - Local files: No restrictions.
277
+ Default:
278
+ - Inline HTML: Disabled.
279
+ - JS: Restricted to extras.
280
+ - Remote files: Restricted to images and fonts via https.
281
+ - Local files: Restricted to files inside the base directory.
282
+ Strict:
283
+ - Inline HTML: Disabled.
284
+ - JS: Disabled (may break some extras).
285
+ - Remote files: Disabled (internet access completely disabled).
286
+ - Local files: Restricted to files inside the base directory.
229
287
  """
230
288
  self.md_path = Path(markdown_path).absolute()
231
289
  self.css_path = Path(css_path).absolute() if css_path else None
@@ -323,7 +381,21 @@ def _generate_pdf_with_playwright(
323
381
  dump_html (bool, optional): Whether to dump the HTML content to a file.
324
382
  nonce (str, optional): Nonce for Content Security Policy.
325
383
  security_level (str=None): Security level to use.
326
- "strict" disables JS, inline HTML and internet access.
384
+ Basic:
385
+ - Inline HTML: No restrictions.
386
+ - JS: Restricted to extras.
387
+ - Remote files: Restricted to images and fonts via https.
388
+ - Local files: No restrictions.
389
+ Default:
390
+ - Inline HTML: Disabled.
391
+ - JS: Restricted to extras.
392
+ - Remote files: Restricted to images and fonts via https.
393
+ - Local files: Restricted to files inside the base directory.
394
+ Strict:
395
+ - Inline HTML: Disabled.
396
+ - JS: Disabled (may break some extras).
397
+ - Remote files: Disabled (internet access completely disabled).
398
+ - Local files: Restricted to files inside the base directory.
327
399
  """
328
400
  if nonce is None:
329
401
  raise ValueError("A nonce must be provided for CSP generation.")
@@ -355,6 +427,11 @@ def _generate_pdf_with_playwright(
355
427
 
356
428
  try:
357
429
  if base_dir:
430
+ if security_level != "basic":
431
+ resolved_base = base_dir.resolve()
432
+ page.route(
433
+ "**/*", lambda route: _route_handler(route, resolved_base)
434
+ )
358
435
  page.goto(base_dir.as_uri())
359
436
 
360
437
  page.set_content(full_html, wait_until="networkidle", timeout=30000)
@@ -369,3 +446,24 @@ def _generate_pdf_with_playwright(
369
446
 
370
447
  finally:
371
448
  browser.close()
449
+
450
+
451
+ def _route_handler(route, resolved_base):
452
+ """
453
+ Route handler to prevent access to files outside the base directory.
454
+
455
+ Args:
456
+ route (Route): Route to handle.
457
+ resolved_base (Path): Base directory for resolving relative paths in HTML.
458
+ """
459
+ parsed = urlparse(route.request.url)
460
+
461
+ if parsed.scheme == "file":
462
+ try:
463
+ file_path = Path(urllib.request.url2pathname(parsed.path)).resolve()
464
+ if not file_path.is_relative_to(resolved_base):
465
+ return route.abort("accessdenied")
466
+ except Exception:
467
+ return route.abort("accessdenied")
468
+
469
+ route.continue_()
@@ -105,6 +105,9 @@ class SyntaxHighlightExtra(ExtraFeature):
105
105
  code = match.group("code")
106
106
 
107
107
  try:
108
+ # Unescape is used here because markdown-it escapes "stray" > characters
109
+ # when html is enabled (security_level=basic) and code blocks can contain
110
+ # these characters.
108
111
  highlighted = highlight(
109
112
  html.unescape(code),
110
113
  get_lexer_by_name(lang),
@@ -158,6 +161,9 @@ class InlineMathExtra(ExtraFeature):
158
161
  str: Tag representing the LaTeX math expression.
159
162
  """
160
163
  content = match.group("content")
164
+
165
+ # Unescape is used here because markdown-it escapes "stray" > characters when
166
+ # html is enabled (security_level=basic) and math contains these characters.
161
167
  converted = mathml.convert(html.unescape(content))
162
168
  return converted
163
169
 
@@ -182,6 +188,9 @@ class BlockMathExtra(ExtraFeature):
182
188
  str: Tag representing the LaTeX math expression.
183
189
  """
184
190
  content = match.group("content")
191
+
192
+ # Unescape is used here because markdown-it escapes "stray" > characters when
193
+ # html is enabled (security_level=basic) and math contains these characters.
185
194
  converted = mathml.convert(html.unescape(content), display="block")
186
195
  return converted
187
196
 
@@ -237,7 +246,7 @@ class TocExtra(ExtraFeature):
237
246
 
238
247
  list_item = soup.new_tag("li")
239
248
  link = soup.new_tag("a", href=f"#{header['id']}")
240
- link.string = header.get_text(strip=True)
249
+ link.string = header.get_text(strip=True, separator=" ")
241
250
  list_item.append(link)
242
251
 
243
252
  active_list[level].append(list_item)
@@ -272,6 +281,9 @@ class MermaidExtra(ExtraFeature):
272
281
  str: SVG tag representing the mermaid diagram.
273
282
  """
274
283
  content = match.group("content")
284
+
285
+ # Unescape is used here because markdown-it escapes "stray" > characters when
286
+ # html is enabled (security_level=basic) and mermaid relies on these characters.
275
287
  content = html.unescape(content)
276
288
  return f'<div class="mermaid">{content}</div>'
277
289
 
@@ -318,6 +330,10 @@ class VegaExtra(ExtraFeature):
318
330
  data_spec["values"] = table.to_dict(orient="records")
319
331
 
320
332
  content = match.group("content")
333
+
334
+ # Unescape is used here because markdown-it escapes "stray" > characters
335
+ # when html is enabled (security_level=basic) and vega-lite specs can contain
336
+ # these characters.
321
337
  content = html.unescape(content)
322
338
  spec = None
323
339
 
@@ -376,6 +392,10 @@ class SchemDrawExtra(ExtraFeature):
376
392
  str: SVG tag representing the schemdraw diagram.
377
393
  """
378
394
  content = match.group("content")
395
+
396
+ # Unescape is used here because markdown-it escapes "stray" > characters
397
+ # when html is enabled (security_level=basic) and schemdraw specs can contain
398
+ # these characters.
379
399
  content = html.unescape(content)
380
400
  try:
381
401
  diagram = from_yaml_string(content)
@@ -462,6 +482,10 @@ class DynamicQueryExtra(ExtraFeature):
462
482
  Returns:
463
483
  str: The query result as text or an HTML table.
464
484
  """
485
+
486
+ # Unescape is used here because markdown-it escapes "stray" > characters
487
+ # when html is enabled (security_level=basic) and sql queries can contain
488
+ # these characters.
465
489
  expression = html.unescape(match.group("expression"))
466
490
  try:
467
491
  conn = _get_duckdb_connection(memory)
@@ -510,9 +534,10 @@ def _render_html_table(columns, rows):
510
534
  Returns:
511
535
  str: An HTML <table> string.
512
536
  """
513
- header_cells = "".join(f"<th>{col}</th>" for col in columns)
537
+ header_cells = "".join(f"<th>{html.escape(str(col))}</th>" for col in columns)
514
538
  body_rows = "".join(
515
- "<tr>" + "".join(f"<td>{val}</td>" for val in row) + "</tr>" for row in rows
539
+ "<tr>" + "".join(f"<td>{html.escape(str(val))}</td>" for val in row) + "</tr>"
540
+ for row in rows
516
541
  )
517
542
  return (
518
543
  f"<table><thead><tr>{header_cells}</tr></thead>"
@@ -149,8 +149,20 @@ def get_usage():
149
149
  f" Supported extras:\n{extras_str}\n"
150
150
  f" {option_five}\n"
151
151
  " Specify the security level.\n"
152
- " Strict mode disables inline HTML, internet access and JS,\n"
153
- " but local files can still be referenced.\n"
154
- " This improves security, but will break some extras.\n"
152
+ " Basic:\n"
153
+ " - Inline HTML: No restrictions.\n"
154
+ " - JS: Restricted to extras.\n"
155
+ " - Remote files: Restricted to images and fonts via https.\n"
156
+ " - Local files: No restrictions.\n"
157
+ " Default:\n"
158
+ " - Inline HTML: Disabled.\n"
159
+ " - JS: Restricted to extras.\n"
160
+ " - Remote files: Restricted to images and fonts via https.\n"
161
+ " - Local files: Restricted to files inside the base directory.\n"
162
+ " Strict:\n"
163
+ " - Inline HTML: Disabled.\n"
164
+ " - JS: Disabled (may break some extras).\n"
165
+ " - Remote files: Disabled (internet access completely disabled).\n"
166
+ " - Local files: Restricted to files inside the base directory.\n"
155
167
  )
156
168
  return usage
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "markdown_convert"
7
- version = "2.0.8"
7
+ version = "2.0.10"
8
8
  description = "Convert Markdown files to PDF from your command line."
9
9
  authors = [{ name = "Julio Cabria", email = "juliocabria@tutanota.com" }]
10
10
  license = { text = "GPL-2.0-only" }