The main Bubble source repository. Contains the Bubble API server, the web UI, documentation and utilities. https://getbubblenow.com
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

bubble_modify.py 8.6 KiB

4 år sedan
4 år sedan
4 år sedan
4 år sedan
4 år sedan
4 år sedan
4 år sedan
4 år sedan
4 år sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #
  2. # Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
  3. #
  4. import re
  5. import requests
  6. import urllib
  7. import traceback
  8. from mitmproxy.net.http import Headers
  9. from bubble_config import bubble_port, bubble_host_alias
  10. from bubble_api import CTX_BUBBLE_MATCHERS, CTX_BUBBLE_ABORT, BUBBLE_URI_PREFIX, \
  11. CTX_BUBBLE_REQUEST_ID, CTX_CONTENT_LENGTH, CTX_CONTENT_LENGTH_SENT, bubble_log, get_flow_ctx, add_flow_ctx
  12. BUFFER_SIZE = 4096
  13. HEADER_CONTENT_TYPE = 'Content-Type'
  14. HEADER_CONTENT_LENGTH = 'Content-Length'
  15. HEADER_CONTENT_ENCODING = 'Content-Encoding'
  16. HEADER_TRANSFER_ENCODING = 'Transfer-Encoding'
  17. BINARY_DATA_HEADER = {HEADER_CONTENT_TYPE: 'application/octet-stream'}
  18. def filter_chunk(chunk, req_id, last, content_encoding=None, content_type=None, content_length=None):
  19. url = 'http://127.0.0.1:' + bubble_port + '/api/filter/apply/' + req_id
  20. params_added = False
  21. if chunk and content_type:
  22. params_added = True
  23. url = (url
  24. + '?type=' + urllib.parse.quote_plus(content_type))
  25. if content_encoding:
  26. url = url + '&encoding=' + urllib.parse.quote_plus(content_encoding)
  27. if content_length:
  28. url = url + '&length=' + str(content_length)
  29. if last:
  30. if params_added:
  31. url = url + '&last=true'
  32. else:
  33. url = url + '?last=true'
  34. bubble_log('filter_chunk: url='+url)
  35. response = requests.post(url, data=chunk, headers=BINARY_DATA_HEADER)
  36. if not response.ok:
  37. err_message = 'filter_chunk: Error fetching ' + url + ', HTTP status ' + str(response.status_code)
  38. bubble_log(err_message)
  39. return b''
  40. return response.content
  41. def bubble_filter_chunks(flow, chunks, req_id, content_encoding, content_type):
  42. """
  43. chunks is a generator that can be used to iterate over all chunks.
  44. """
  45. first = True
  46. content_length = get_flow_ctx(flow, CTX_CONTENT_LENGTH)
  47. try:
  48. for chunk in chunks:
  49. if content_length:
  50. bytes_sent = get_flow_ctx(flow, CTX_CONTENT_LENGTH_SENT)
  51. chunk_len = len(chunk)
  52. last = chunk_len + bytes_sent >= content_length
  53. bubble_log('bubble_filter_chunks: content_length = '+str(content_length)+', bytes_sent = '+str(bytes_sent))
  54. add_flow_ctx(flow, CTX_CONTENT_LENGTH_SENT, bytes_sent + chunk_len)
  55. else:
  56. last = False
  57. if first:
  58. yield filter_chunk(chunk, req_id, last, content_encoding, content_type, content_length)
  59. first = False
  60. else:
  61. yield filter_chunk(chunk, req_id, last)
  62. if not content_length:
  63. yield filter_chunk(None, req_id, True) # get the last bits of data
  64. except Exception as e:
  65. bubble_log('bubble_filter_chunks: exception='+repr(e))
  66. traceback.print_exc()
  67. yield None
  68. def bubble_modify(flow, req_id, content_encoding, content_type):
  69. return lambda chunks: bubble_filter_chunks(flow, chunks, req_id, content_encoding, content_type)
  70. def send_bubble_response(response):
  71. for chunk in response.iter_content(8192):
  72. yield chunk
  73. def responseheaders(flow):
  74. if flow.request.path and flow.request.path.startswith(BUBBLE_URI_PREFIX):
  75. uri = 'http://127.0.0.1:' + bubble_port + '/' + flow.request.path[len(BUBBLE_URI_PREFIX):]
  76. bubble_log('responseheaders: sending special bubble request to '+uri)
  77. headers = {
  78. 'Accept' : 'application/json',
  79. 'Content-Type': 'application/json'
  80. }
  81. response = None
  82. if flow.request.method == 'GET':
  83. response = requests.get(uri, headers=headers, stream=True)
  84. elif flow.request.method == 'POST':
  85. bubble_log('responseheaders: special bubble request: POST content is '+str(flow.request.content))
  86. headers['Content-Length'] = str(len(flow.request.content))
  87. response = requests.post(uri, data=flow.request.content, headers=headers)
  88. else:
  89. bubble_log('responseheaders: special bubble request: method '+flow.request.method+' not supported')
  90. if response is not None:
  91. bubble_log('responseheaders: special bubble request: response status = '+str(response.status_code))
  92. flow.response.headers = Headers()
  93. for key, value in response.headers.items():
  94. flow.response.headers[key] = value
  95. flow.response.status_code = response.status_code
  96. flow.response.stream = lambda chunks: send_bubble_response(response)
  97. else:
  98. abort_code = get_flow_ctx(flow, CTX_BUBBLE_ABORT)
  99. if abort_code is not None:
  100. bubble_log('responseheaders: aborting request with HTTP status '+str(abort_code))
  101. flow.response.headers = Headers()
  102. flow.response.status_code = abort_code
  103. flow.response.stream = lambda chunks: []
  104. else:
  105. req_id = get_flow_ctx(flow, CTX_BUBBLE_REQUEST_ID)
  106. matchers = get_flow_ctx(flow, CTX_BUBBLE_MATCHERS)
  107. if req_id is not None and matchers is not None:
  108. bubble_log('responseheaders: req_id='+req_id+' with matchers: '+repr(matchers))
  109. if HEADER_CONTENT_TYPE in flow.response.headers:
  110. content_type = flow.response.headers[HEADER_CONTENT_TYPE]
  111. if matchers:
  112. any_content_type_matches = False
  113. for m in matchers:
  114. if 'contentTypeRegex' in m:
  115. typeRegex = m['contentTypeRegex']
  116. if typeRegex is None:
  117. typeRegex = '^text/html.*'
  118. if re.match(typeRegex, content_type):
  119. any_content_type_matches = True
  120. bubble_log('responseheaders: req_id='+req_id+' found at least one matcher for content_type ('+content_type+'), filtering')
  121. break
  122. if not any_content_type_matches:
  123. bubble_log('responseheaders: req_id='+req_id+' no matchers for content_type ('+content_type+'), passing thru')
  124. return
  125. if HEADER_CONTENT_ENCODING in flow.response.headers:
  126. content_encoding = flow.response.headers[HEADER_CONTENT_ENCODING]
  127. else:
  128. content_encoding = None
  129. content_length_value = flow.response.headers.pop(HEADER_CONTENT_LENGTH, None)
  130. bubble_log('responseheaders: req_id='+req_id+' content_encoding='+repr(content_encoding) + ', content_type='+repr(content_type))
  131. flow.response.stream = bubble_modify(flow, req_id, content_encoding, content_type)
  132. if content_length_value:
  133. flow.response.headers['transfer-encoding'] = 'chunked'
  134. # find server_conn to set fake_chunks on
  135. if flow.live and flow.live.ctx:
  136. ctx = flow.live.ctx
  137. while not hasattr(ctx, 'server_conn'):
  138. if hasattr(ctx, 'ctx'):
  139. ctx = ctx.ctx
  140. else:
  141. bubble_log('responseheaders: error finding server_conn. last ctx has no further ctx. type='+str(type(ctx))+' vars='+str(vars(ctx)))
  142. return
  143. if not hasattr(ctx, 'server_conn'):
  144. bubble_log('responseheaders: error finding server_conn. ctx type='+str(type(ctx))+' vars='+str(vars(ctx)))
  145. return
  146. content_length = int(content_length_value)
  147. ctx.server_conn.rfile.fake_chunks = content_length
  148. add_flow_ctx(flow, CTX_CONTENT_LENGTH, content_length)
  149. add_flow_ctx(flow, CTX_CONTENT_LENGTH_SENT, 0)
  150. else:
  151. bubble_log('responseheaders: no matchers, passing thru')
  152. pass
  153. else:
  154. bubble_log('responseheaders: no '+HEADER_CONTENT_TYPE +' header, passing thru')
  155. pass
  156. else:
  157. bubble_log('responseheaders: no '+CTX_BUBBLE_MATCHERS +' in ctx, passing thru')
  158. pass