mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +00:00 
			
		
		
		
	If the client has an old version of the code which is not present on the server, don't throw a 500; instead, default to the same `unable to look up in source map` message is used when the line numbers don't line up.
		
			
				
	
	
		
			73 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			73 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import os
 | |
| import re
 | |
| from typing import Dict, List, Optional
 | |
| 
 | |
| import sourcemap
 | |
| 
 | |
| from zerver.lib.pysa import mark_sanitized
 | |
| 
 | |
| 
 | |
| class SourceMap:
 | |
|     """Map (line, column) pairs from generated to source file."""
 | |
| 
 | |
|     def __init__(self, sourcemap_dirs: List[str]) -> None:
 | |
|         self._dirs = sourcemap_dirs
 | |
|         self._indices: Dict[str, sourcemap.SourceMapDecoder] = {}
 | |
| 
 | |
|     def _index_for(self, minified_src: str) -> Optional[sourcemap.SourceMapDecoder]:
 | |
|         """Return the source map index for minified_src, loading it if not
 | |
|         already loaded."""
 | |
| 
 | |
|         # Prevent path traversal
 | |
|         assert ".." not in minified_src and "/" not in minified_src
 | |
| 
 | |
|         if minified_src not in self._indices:
 | |
|             for source_dir in self._dirs:
 | |
|                 filename = os.path.join(source_dir, minified_src + ".map")
 | |
|                 if os.path.isfile(filename):
 | |
|                     # Use 'mark_sanitized' to force Pysa to ignore the fact that
 | |
|                     # 'filename' is user controlled. While putting user
 | |
|                     # controlled data into a filesystem operation is bad, in
 | |
|                     # this case it's benign because 'filename' can't traverse
 | |
|                     # directories outside of the pre-configured 'sourcemap_dirs'
 | |
|                     # (due to the above assertions) and will always end in
 | |
|                     # '.map'. Additionally, the result of this function is used
 | |
|                     # for error logging and not returned to the user, so
 | |
|                     # controlling the loaded file would not be useful to an
 | |
|                     # attacker.
 | |
|                     with open(mark_sanitized(filename)) as fp:
 | |
|                         self._indices[minified_src] = sourcemap.load(fp)
 | |
|                         break
 | |
| 
 | |
|         return self._indices.get(minified_src)
 | |
| 
 | |
|     def annotate_stacktrace(self, stacktrace: str) -> str:
 | |
|         out: str = ""
 | |
|         for ln in stacktrace.splitlines():
 | |
|             out += ln + "\n"
 | |
|             match = re.search(r"/static/webpack-bundles/([^:]+):(\d+):(\d+)", ln)
 | |
|             if match:
 | |
|                 # Get the appropriate source map for the minified file.
 | |
|                 minified_src = match.groups()[0]
 | |
|                 index = self._index_for(minified_src)
 | |
|                 if index is None:
 | |
|                     out += "       [Unable to look up in source map]\n"
 | |
|                     continue
 | |
| 
 | |
|                 gen_line, gen_col = list(map(int, match.groups()[1:3]))
 | |
|                 # The sourcemap lib is 0-based, so subtract 1 from line and col.
 | |
|                 try:
 | |
|                     result = index.lookup(line=gen_line - 1, column=gen_col - 1)
 | |
|                     display_src = result.src
 | |
|                     if display_src is not None:
 | |
|                         webpack_prefix = "webpack:///"
 | |
|                         if display_src.startswith(webpack_prefix):
 | |
|                             display_src = display_src[len(webpack_prefix) :]
 | |
|                         out += f"       = {display_src} line {result.src_line+1} column {result.src_col+1}\n"
 | |
|                 except IndexError:
 | |
|                     out += "       [Unable to look up in source map]\n"
 | |
| 
 | |
|             if ln.startswith("    at"):
 | |
|                 out += "\n"
 | |
|         return out
 |