PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/Documentation/sphinx/kernel_include.py

https://github.com/kvaneesh/linux
Python | 190 lines | 186 code | 1 blank | 3 comment | 0 complexity | 3abc98e2ad57d9b4d969087fd0c119e2 MD5 | raw file
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8; mode: python -*-
  3. # pylint: disable=R0903, C0330, R0914, R0912, E0401
  4. u"""
  5. kernel-include
  6. ~~~~~~~~~~~~~~
  7. Implementation of the ``kernel-include`` reST-directive.
  8. :copyright: Copyright (C) 2016 Markus Heiser
  9. :license: GPL Version 2, June 1991 see linux/COPYING for details.
  10. The ``kernel-include`` reST-directive is a replacement for the ``include``
  11. directive. The ``kernel-include`` directive expand environment variables in
  12. the path name and allows to include files from arbitrary locations.
  13. .. hint::
  14. Including files from arbitrary locations (e.g. from ``/etc``) is a
  15. security risk for builders. This is why the ``include`` directive from
  16. docutils *prohibit* pathnames pointing to locations *above* the filesystem
  17. tree where the reST document with the include directive is placed.
  18. Substrings of the form $name or ${name} are replaced by the value of
  19. environment variable name. Malformed variable names and references to
  20. non-existing variables are left unchanged.
  21. """
  22. # ==============================================================================
  23. # imports
  24. # ==============================================================================
  25. import os.path
  26. from docutils import io, nodes, statemachine
  27. from docutils.utils.error_reporting import SafeString, ErrorString
  28. from docutils.parsers.rst import directives
  29. from docutils.parsers.rst.directives.body import CodeBlock, NumberLines
  30. from docutils.parsers.rst.directives.misc import Include
  31. __version__ = '1.0'
  32. # ==============================================================================
  33. def setup(app):
  34. # ==============================================================================
  35. app.add_directive("kernel-include", KernelInclude)
  36. return dict(
  37. version = __version__,
  38. parallel_read_safe = True,
  39. parallel_write_safe = True
  40. )
  41. # ==============================================================================
  42. class KernelInclude(Include):
  43. # ==============================================================================
  44. u"""KernelInclude (``kernel-include``) directive"""
  45. def run(self):
  46. path = os.path.realpath(
  47. os.path.expandvars(self.arguments[0]))
  48. # to get a bit security back, prohibit /etc:
  49. if path.startswith(os.sep + "etc"):
  50. raise self.severe(
  51. 'Problems with "%s" directive, prohibited path: %s'
  52. % (self.name, path))
  53. self.arguments[0] = path
  54. #return super(KernelInclude, self).run() # won't work, see HINTs in _run()
  55. return self._run()
  56. def _run(self):
  57. """Include a file as part of the content of this reST file."""
  58. # HINT: I had to copy&paste the whole Include.run method. I'am not happy
  59. # with this, but due to security reasons, the Include.run method does
  60. # not allow absolute or relative pathnames pointing to locations *above*
  61. # the filesystem tree where the reST document is placed.
  62. if not self.state.document.settings.file_insertion_enabled:
  63. raise self.warning('"%s" directive disabled.' % self.name)
  64. source = self.state_machine.input_lines.source(
  65. self.lineno - self.state_machine.input_offset - 1)
  66. source_dir = os.path.dirname(os.path.abspath(source))
  67. path = directives.path(self.arguments[0])
  68. if path.startswith('<') and path.endswith('>'):
  69. path = os.path.join(self.standard_include_path, path[1:-1])
  70. path = os.path.normpath(os.path.join(source_dir, path))
  71. # HINT: this is the only line I had to change / commented out:
  72. #path = utils.relative_path(None, path)
  73. path = nodes.reprunicode(path)
  74. encoding = self.options.get(
  75. 'encoding', self.state.document.settings.input_encoding)
  76. e_handler=self.state.document.settings.input_encoding_error_handler
  77. tab_width = self.options.get(
  78. 'tab-width', self.state.document.settings.tab_width)
  79. try:
  80. self.state.document.settings.record_dependencies.add(path)
  81. include_file = io.FileInput(source_path=path,
  82. encoding=encoding,
  83. error_handler=e_handler)
  84. except UnicodeEncodeError as error:
  85. raise self.severe('Problems with "%s" directive path:\n'
  86. 'Cannot encode input file path "%s" '
  87. '(wrong locale?).' %
  88. (self.name, SafeString(path)))
  89. except IOError as error:
  90. raise self.severe('Problems with "%s" directive path:\n%s.' %
  91. (self.name, ErrorString(error)))
  92. startline = self.options.get('start-line', None)
  93. endline = self.options.get('end-line', None)
  94. try:
  95. if startline or (endline is not None):
  96. lines = include_file.readlines()
  97. rawtext = ''.join(lines[startline:endline])
  98. else:
  99. rawtext = include_file.read()
  100. except UnicodeError as error:
  101. raise self.severe('Problem with "%s" directive:\n%s' %
  102. (self.name, ErrorString(error)))
  103. # start-after/end-before: no restrictions on newlines in match-text,
  104. # and no restrictions on matching inside lines vs. line boundaries
  105. after_text = self.options.get('start-after', None)
  106. if after_text:
  107. # skip content in rawtext before *and incl.* a matching text
  108. after_index = rawtext.find(after_text)
  109. if after_index < 0:
  110. raise self.severe('Problem with "start-after" option of "%s" '
  111. 'directive:\nText not found.' % self.name)
  112. rawtext = rawtext[after_index + len(after_text):]
  113. before_text = self.options.get('end-before', None)
  114. if before_text:
  115. # skip content in rawtext after *and incl.* a matching text
  116. before_index = rawtext.find(before_text)
  117. if before_index < 0:
  118. raise self.severe('Problem with "end-before" option of "%s" '
  119. 'directive:\nText not found.' % self.name)
  120. rawtext = rawtext[:before_index]
  121. include_lines = statemachine.string2lines(rawtext, tab_width,
  122. convert_whitespace=True)
  123. if 'literal' in self.options:
  124. # Convert tabs to spaces, if `tab_width` is positive.
  125. if tab_width >= 0:
  126. text = rawtext.expandtabs(tab_width)
  127. else:
  128. text = rawtext
  129. literal_block = nodes.literal_block(rawtext, source=path,
  130. classes=self.options.get('class', []))
  131. literal_block.line = 1
  132. self.add_name(literal_block)
  133. if 'number-lines' in self.options:
  134. try:
  135. startline = int(self.options['number-lines'] or 1)
  136. except ValueError:
  137. raise self.error(':number-lines: with non-integer '
  138. 'start value')
  139. endline = startline + len(include_lines)
  140. if text.endswith('\n'):
  141. text = text[:-1]
  142. tokens = NumberLines([([], text)], startline, endline)
  143. for classes, value in tokens:
  144. if classes:
  145. literal_block += nodes.inline(value, value,
  146. classes=classes)
  147. else:
  148. literal_block += nodes.Text(value, value)
  149. else:
  150. literal_block += nodes.Text(text, text)
  151. return [literal_block]
  152. if 'code' in self.options:
  153. self.options['source'] = path
  154. codeblock = CodeBlock(self.name,
  155. [self.options.pop('code')], # arguments
  156. self.options,
  157. include_lines, # content
  158. self.lineno,
  159. self.content_offset,
  160. self.block_text,
  161. self.state,
  162. self.state_machine)
  163. return codeblock.run()
  164. self.state_machine.insert_input(include_lines, path)
  165. return []