| 80 | |
| 81 | |
| 82 | class MapLocal: |
| 83 | def __init__(self) -> None: |
| 84 | self.replacements: list[MapLocalSpec] = [] |
| 85 | |
| 86 | def load(self, loader): |
| 87 | loader.add_option( |
| 88 | "map_local", |
| 89 | Sequence[str], |
| 90 | [], |
| 91 | """ |
| 92 | Map remote resources to a local file using a pattern of the form |
| 93 | "[/flow-filter]/url-regex/file-or-directory-path", where the |
| 94 | separator can be any character. |
| 95 | """, |
| 96 | ) |
| 97 | |
| 98 | def configure(self, updated): |
| 99 | if "map_local" in updated: |
| 100 | self.replacements = [] |
| 101 | for option in ctx.options.map_local: |
| 102 | try: |
| 103 | spec = parse_map_local_spec(option) |
| 104 | except ValueError as e: |
| 105 | raise exceptions.OptionsError( |
| 106 | f"Cannot parse map_local option {option}: {e}" |
| 107 | ) from e |
| 108 | |
| 109 | self.replacements.append(spec) |
| 110 | |
| 111 | def request(self, flow: http.HTTPFlow) -> None: |
| 112 | if flow.response or flow.error or not flow.live: |
| 113 | return |
| 114 | |
| 115 | url = flow.request.pretty_url |
| 116 | |
| 117 | all_candidates = [] |
| 118 | for spec in self.replacements: |
| 119 | if spec.matches(flow) and re.search(spec.regex, url): |
| 120 | if spec.local_path.is_file(): |
| 121 | candidates = [spec.local_path] |
| 122 | else: |
| 123 | candidates = file_candidates(url, spec) |
| 124 | all_candidates.extend(candidates) |
| 125 | |
| 126 | local_file = None |
| 127 | for candidate in candidates: |
| 128 | if candidate.is_file(): |
| 129 | local_file = candidate |
| 130 | break |
| 131 | |
| 132 | if local_file: |
| 133 | headers = {"Server": version.MITMPROXY} |
| 134 | mimetype = mimetypes.guess_type(str(local_file))[0] |
| 135 | if mimetype: |
| 136 | headers["Content-Type"] = mimetype |
| 137 | |
| 138 | try: |
| 139 | contents = local_file.read_bytes() |
no outgoing calls
searching dependent graphs…