@@ -63,8 +63,7 @@ def get_final_doctype(self):
63
63
allow_symlinks = True ,
64
64
base_path = self .project_path ,
65
65
) as fh :
66
- # Use ``.safe_load()`` since ``mkdocs.yml`` is an untrusted source.
67
- config = yaml .safe_load (fh )
66
+ config = yaml_load_safely (fh )
68
67
use_directory_urls = config .get ("use_directory_urls" , True )
69
68
return MKDOCS if use_directory_urls else MKDOCS_HTML
70
69
@@ -120,3 +119,62 @@ def build(self):
120
119
class MkdocsHTML (BaseMkdocs ):
121
120
builder = "build"
122
121
build_dir = "_readthedocs/html"
122
+
123
+
124
+ class ProxyPythonName (yaml .YAMLObject ):
125
+ def __init__ (self , value ):
126
+ self .value = value
127
+
128
+ def __eq__ (self , other ):
129
+ return self .value == other .value
130
+
131
+
132
+ class SafeLoader (yaml .SafeLoader ): # pylint: disable=too-many-ancestors
133
+
134
+ """
135
+ Safe YAML loader.
136
+
137
+ This loader parses special ``!!python/name:`` tags without actually
138
+ importing or executing code. Every other special tag is ignored.
139
+
140
+ Borrowed from https://stackoverflow.com/a/57121993
141
+ Issue https://github.com/readthedocs/readthedocs.org/issues/7461
142
+ """
143
+
144
+ def ignore_unknown (self , node ): # pylint: disable=unused-argument
145
+ return None
146
+
147
+ def construct_python_name (self , suffix , node ): # pylint: disable=unused-argument
148
+ return ProxyPythonName (suffix )
149
+
150
+
151
+ class SafeDumper (yaml .SafeDumper ):
152
+
153
+ """
154
+ Safe YAML dumper.
155
+
156
+ This dumper allows to avoid losing values of special tags that
157
+ were parsed by our safe loader.
158
+ """
159
+
160
+ def represent_name (self , data ):
161
+ return self .represent_scalar ("tag:yaml.org,2002:python/name:" + data .value , "" )
162
+
163
+
164
+ SafeLoader .add_multi_constructor (
165
+ "tag:yaml.org,2002:python/name:" , SafeLoader .construct_python_name
166
+ )
167
+ SafeLoader .add_constructor (None , SafeLoader .ignore_unknown )
168
+ SafeDumper .add_representer (ProxyPythonName , SafeDumper .represent_name )
169
+
170
+
171
+ def yaml_load_safely (content ):
172
+ """
173
+ Uses ``SafeLoader`` loader to skip unknown tags.
174
+
175
+ When a YAML contains ``!!python/name:int`` it will store the ``int``
176
+ suffix temporarily to be able to re-dump it later. We need this to avoid
177
+ executing random code, but still support these YAML files without
178
+ information loss.
179
+ """
180
+ return yaml .load (content , Loader = SafeLoader )
0 commit comments