diff options
81 files changed, 3122 insertions, 5300 deletions
@@ -6,7 +6,7 @@ MANIFEST *.swp *.swo *.egg-info/ -.coverage +.coverage* .idea .cache/ .tox*/ diff --git a/docs/dev/models.rst b/docs/dev/models.rst index 7260f1f7..a333fb06 100644 --- a/docs/dev/models.rst +++ b/docs/dev/models.rst @@ -39,6 +39,8 @@ Datastructures .. autoclass:: Response + .. automethod:: make + .. rubric:: Data .. autoattribute:: http_version .. autoattribute:: status_code diff --git a/docs/scripting/inlinescripts.rst b/docs/scripting/inlinescripts.rst index bc9d5ff5..e1c01b17 100644 --- a/docs/scripting/inlinescripts.rst +++ b/docs/scripting/inlinescripts.rst @@ -126,6 +126,18 @@ HTTP Events :param HTTPFlow flow: The flow containing the error. It is guaranteed to have non-None ``error`` attribute. +WebSockets Events +^^^^^^^^^^^^^^^^^ + +.. py:function:: websockets_handshake(context, flow) + + Called when a client wants to establish a WebSockets connection. + The WebSockets-specific headers can be manipulated to manipulate the handshake. + The ``flow`` object is guaranteed to have a non-None ``request`` attribute. + + :param HTTPFlow flow: The flow containing the request which has been received. + The object is guaranteed to have a non-None ``request`` attribute. + TCP Events ^^^^^^^^^^ diff --git a/examples/flowbasic b/examples/flowbasic index 74af4e08..67c6f596 100755 --- a/examples/flowbasic +++ b/examples/flowbasic @@ -8,7 +8,7 @@ Note that request and response messages are not automatically replied to, so we need to implement handlers to do this. """ -from mitmproxy import flow, controller +from mitmproxy import flow, controller, options from mitmproxy.proxy import ProxyServer, ProxyConfig @@ -21,21 +21,23 @@ class MyMaster(flow.FlowMaster): @controller.handler def request(self, f): - f = flow.FlowMaster.request(self, f) - print(f) + print("request", f) @controller.handler def response(self, f): - f = flow.FlowMaster.response(self, f) - print(f) + print("response", f) + @controller.handler + def error(self, f): + print("error", f) + + @controller.handler + def log(self, l): + print("log", l.msg) -config = ProxyConfig( - port=8080, - # use ~/.mitmproxy/mitmproxy-ca.pem as default CA file. - cadir="~/.mitmproxy/" -) +opts = options.Options(cadir="~/.mitmproxy/") +config = ProxyConfig(opts) state = flow.State() server = ProxyServer(config) -m = MyMaster(server, state) +m = MyMaster(opts, server, state) m.run() diff --git a/examples/har_dump.py b/examples/har_dump.py new file mode 100644 index 00000000..95090edb --- /dev/null +++ b/examples/har_dump.py @@ -0,0 +1,216 @@ +""" +This inline script can be used to dump flows as HAR files. +""" + + +import pprint +import json +import sys +import base64 +import zlib + +from datetime import datetime +import pytz + +import mitmproxy + +from netlib import version +from netlib import strutils +from netlib.http import cookies + +HAR = {} + +# A list of server seen till now is maintained so we can avoid +# using 'connect' time for entries that use an existing connection. +SERVERS_SEEN = set() + + +def start(): + """ + Called once on script startup before any other events. + """ + if len(sys.argv) != 2: + raise ValueError( + 'Usage: -s "har_dump.py filename" ' + '(- will output to stdout, filenames ending with .zhar ' + 'will result in compressed har)' + ) + + HAR.update({ + "log": { + "version": "1.2", + "creator": { + "name": "mitmproxy har_dump", + "version": "0.1", + "comment": "mitmproxy version %s" % version.MITMPROXY + }, + "entries": [] + } + }) + + +def response(flow): + """ + Called when a server response has been received. + """ + + # -1 indicates that these values do not apply to current request + ssl_time = -1 + connect_time = -1 + + if flow.server_conn and flow.server_conn not in SERVERS_SEEN: + connect_time = (flow.server_conn.timestamp_tcp_setup - + flow.server_conn.timestamp_start) + + if flow.server_conn.timestamp_ssl_setup is not None: + ssl_time = (flow.server_conn.timestamp_ssl_setup - + flow.server_conn.timestamp_tcp_setup) + + SERVERS_SEEN.add(flow.server_conn) + + # Calculate raw timings from timestamps. DNS timings can not be calculated + # for lack of a way to measure it. The same goes for HAR blocked. + # mitmproxy will open a server connection as soon as it receives the host + # and port from the client connection. So, the time spent waiting is actually + # spent waiting between request.timestamp_end and response.timestamp_start + # thus it correlates to HAR wait instead. + timings_raw = { + 'send': flow.request.timestamp_end - flow.request.timestamp_start, + 'receive': flow.response.timestamp_end - flow.response.timestamp_start, + 'wait': flow.response.timestamp_start - flow.request.timestamp_end, + 'connect': connect_time, + 'ssl': ssl_time, + } + + # HAR timings are integers in ms, so we re-encode the raw timings to that format. + timings = dict([(k, int(1000 * v)) for k, v in timings_raw.items()]) + + # full_time is the sum of all timings. + # Timings set to -1 will be ignored as per spec. + full_time = sum(v for v in timings.values() if v > -1) + + started_date_time = format_datetime(datetime.utcfromtimestamp(flow.request.timestamp_start)) + + # Response body size and encoding + response_body_size = len(flow.response.raw_content) + response_body_decoded_size = len(flow.response.content) + response_body_compression = response_body_decoded_size - response_body_size + + entry = { + "startedDateTime": started_date_time, + "time": full_time, + "request": { + "method": flow.request.method, + "url": flow.request.url, + "httpVersion": flow.request.http_version, + "cookies": format_request_cookies(flow.request.cookies.fields), + "headers": name_value(flow.request.headers), + "queryString": name_value(flow.request.query or {}), + "headersSize": len(str(flow.request.headers)), + "bodySize": len(flow.request.content), + }, + "response": { + "status": flow.response.status_code, + "statusText": flow.response.reason, + "httpVersion": flow.response.http_version, + "cookies": format_response_cookies(flow.response.cookies.fields), + "headers": name_value(flow.response.headers), + "content": { + "size": response_body_size, + "compression": response_body_compression, + "mimeType": flow.response.headers.get('Content-Type', '') + }, + "redirectURL": flow.response.headers.get('Location', ''), + "headersSize": len(str(flow.response.headers)), + "bodySize": response_body_size, + }, + "cache": {}, + "timings": timings, + } + + # Store binay data as base64 + if strutils.is_mostly_bin(flow.response.content): + b64 = base64.b64encode(flow.response.content) + entry["response"]["content"]["text"] = b64.decode('ascii') + entry["response"]["content"]["encoding"] = "base64" + else: + entry["response"]["content"]["text"] = flow.response.text + + if flow.request.method in ["POST", "PUT", "PATCH"]: + entry["request"]["postData"] = { + "mimeType": flow.request.headers.get("Content-Type", "").split(";")[0], + "text": flow.request.content, + "params": name_value(flow.request.urlencoded_form) + } + + if flow.server_conn: + entry["serverIPAddress"] = str(flow.server_conn.ip_address.address[0]) + + HAR["log"]["entries"].append(entry) + + +def done(): + """ + Called once on script shutdown, after any other events. + """ + dump_file = sys.argv[1] + + if dump_file == '-': + mitmproxy.ctx.log(pprint.pformat(HAR)) + else: + json_dump = json.dumps(HAR, indent=2) + + if dump_file.endswith('.zhar'): + json_dump = zlib.compress(json_dump, 9) + + with open(dump_file, "w") as f: + f.write(json_dump) + + mitmproxy.ctx.log("HAR dump finished (wrote %s bytes to file)" % len(json_dump)) + + +def format_datetime(dt): + return dt.replace(tzinfo=pytz.timezone("UTC")).isoformat() + + +def format_cookies(cookie_list): + rv = [] + + for name, value, attrs in cookie_list: + cookie_har = { + "name": name, + "value": value, + } + + # HAR only needs some attributes + for key in ["path", "domain", "comment"]: + if key in attrs: + cookie_har[key] = attrs[key] + + # These keys need to be boolean! + for key in ["httpOnly", "secure"]: + cookie_har[key] = bool(key in attrs) + + # Expiration time needs to be formatted + expire_ts = cookies.get_expiration_ts(attrs) + if expire_ts is not None: + cookie_har["expires"] = format_datetime(datetime.fromtimestamp(expire_ts)) + + rv.append(cookie_har) + + return rv + + +def format_request_cookies(fields): + return format_cookies(cookies.group_cookies(fields)) + + +def format_response_cookies(fields): + return format_cookies((c[0], c[1].value, c[1].attrs) for c in fields) + + +def name_value(obj): + """ + Convert (key, value) pairs to HAR format. + """ + return [{"name": k, "value": v} for k, v in obj.items()] diff --git a/examples/har_extractor.py b/examples/har_extractor.py deleted file mode 100644 index 76059d8e..00000000 --- a/examples/har_extractor.py +++ /dev/null @@ -1,264 +0,0 @@ -""" - This inline script utilizes harparser.HAR from - https://github.com/JustusW/harparser to generate a HAR log object. -""" -import mitmproxy.ctx -import six -import sys -import pytz -from harparser import HAR - -from datetime import datetime - - -class _HARLog(HAR.log): - # The attributes need to be registered here for them to actually be - # available later via self. This is due to HAREncodable linking __getattr__ - # to __getitem__. Anything that is set only in __init__ will just be added - # as key/value pair to self.__classes__. - __page_list__ = [] - __page_count__ = 0 - __page_ref__ = {} - - def __init__(self, page_list=[]): - self.__page_list__ = page_list - self.__page_count__ = 0 - self.__page_ref__ = {} - - HAR.log.__init__(self, {"version": "1.2", - "creator": {"name": "MITMPROXY HARExtractor", - "version": "0.1", - "comment": ""}, - "pages": [], - "entries": []}) - - def reset(self): - self.__init__(self.__page_list__) - - def add(self, obj): - if isinstance(obj, HAR.pages): - self['pages'].append(obj) - if isinstance(obj, HAR.entries): - self['entries'].append(obj) - - def create_page_id(self): - self.__page_count__ += 1 - return "autopage_%s" % str(self.__page_count__) - - def set_page_ref(self, page, ref): - self.__page_ref__[page] = ref - - def get_page_ref(self, page): - return self.__page_ref__.get(page, None) - - def get_page_list(self): - return self.__page_list__ - - -class Context(object): - pass - -context = Context() - - -def start(): - """ - On start we create a HARLog instance. You will have to adapt this to - suit your actual needs of HAR generation. As it will probably be - necessary to cluster logs by IPs or reset them from time to time. - """ - if sys.version_info >= (3, 0): - raise RuntimeError( - "har_extractor.py does not work on Python 3. " - "Please check out https://github.com/mitmproxy/mitmproxy/issues/1320 " - "if you want to help making this work again." - ) - context.dump_file = None - if len(sys.argv) > 1: - context.dump_file = sys.argv[1] - else: - raise ValueError( - 'Usage: -s "har_extractor.py filename" ' - '(- will output to stdout, filenames ending with .zhar ' - 'will result in compressed har)' - ) - context.HARLog = _HARLog() - context.seen_server = set() - - -def response(flow): - """ - Called when a server response has been received. At the time of this - message both a request and a response are present and completely done. - """ - # Values are converted from float seconds to int milliseconds later. - ssl_time = -.001 - connect_time = -.001 - if flow.server_conn not in context.seen_server: - # Calculate the connect_time for this server_conn. Afterwards add it to - # seen list, in order to avoid the connect_time being present in entries - # that use an existing connection. - connect_time = (flow.server_conn.timestamp_tcp_setup - - flow.server_conn.timestamp_start) - context.seen_server.add(flow.server_conn) - - if flow.server_conn.timestamp_ssl_setup is not None: - # Get the ssl_time for this server_conn as the difference between - # the start of the successful tcp setup and the successful ssl - # setup. If no ssl setup has been made it is left as -1 since it - # doesn't apply to this connection. - ssl_time = (flow.server_conn.timestamp_ssl_setup - - flow.server_conn.timestamp_tcp_setup) - - # Calculate the raw timings from the different timestamps present in the - # request and response object. For lack of a way to measure it dns timings - # can not be calculated. The same goes for HAR blocked: MITMProxy will open - # a server connection as soon as it receives the host and port from the - # client connection. So the time spent waiting is actually spent waiting - # between request.timestamp_end and response.timestamp_start thus it - # correlates to HAR wait instead. - timings_raw = { - 'send': flow.request.timestamp_end - flow.request.timestamp_start, - 'wait': flow.response.timestamp_start - flow.request.timestamp_end, - 'receive': flow.response.timestamp_end - flow.response.timestamp_start, - 'connect': connect_time, - 'ssl': ssl_time - } - - # HAR timings are integers in ms, so we have to re-encode the raw timings to - # that format. - timings = dict([(k, int(1000 * v)) for k, v in six.iteritems(timings_raw)]) - - # The full_time is the sum of all timings. - # Timings set to -1 will be ignored as per spec. - full_time = sum(v for v in timings.values() if v > -1) - - started_date_time = datetime.utcfromtimestamp( - flow.request.timestamp_start).replace(tzinfo=pytz.timezone("UTC")).isoformat() - - request_query_string = [{"name": k, "value": v} - for k, v in flow.request.query or {}] - - response_body_size = len(flow.response.content) - response_body_decoded_size = len(flow.response.content) - response_body_compression = response_body_decoded_size - response_body_size - - entry = HAR.entries({ - "startedDateTime": started_date_time, - "time": full_time, - "request": { - "method": flow.request.method, - "url": flow.request.url, - "httpVersion": flow.request.http_version, - "cookies": format_cookies(flow.request.cookies), - "headers": format_headers(flow.request.headers), - "queryString": request_query_string, - "headersSize": len(str(flow.request.headers)), - "bodySize": len(flow.request.content), - }, - "response": { - "status": flow.response.status_code, - "statusText": flow.response.reason, - "httpVersion": flow.response.http_version, - "cookies": format_cookies(flow.response.cookies), - "headers": format_headers(flow.response.headers), - "content": { - "size": response_body_size, - "compression": response_body_compression, - "mimeType": flow.response.headers.get('Content-Type', '') - }, - "redirectURL": flow.response.headers.get('Location', ''), - "headersSize": len(str(flow.response.headers)), - "bodySize": response_body_size, - }, - "cache": {}, - "timings": timings, - }) - - # If the current url is in the page list of context.HARLog or - # does not have a referrer, we add it as a new pages object. - is_new_page = ( - flow.request.url in context.HARLog.get_page_list() or - flow.request.headers.get('Referer') is None - ) - if is_new_page: - page_id = context.HARLog.create_page_id() - context.HARLog.add( - HAR.pages({ - "startedDateTime": entry['startedDateTime'], - "id": page_id, - "title": flow.request.url, - "pageTimings": {} - }) - ) - context.HARLog.set_page_ref(flow.request.url, page_id) - entry['pageref'] = page_id - - # Lookup the referer in the page_ref of context.HARLog to point this entries - # pageref attribute to the right pages object, then set it as a new - # reference to build a reference tree. - elif context.HARLog.get_page_ref(flow.request.headers.get('Referer')) is not None: - entry['pageref'] = context.HARLog.get_page_ref( - flow.request.headers['Referer'] - ) - context.HARLog.set_page_ref( - flow.request.headers['Referer'], entry['pageref'] - ) - - context.HARLog.add(entry) - - -def done(): - """ - Called once on script shutdown, after any other events. - """ - import pprint - import json - - json_dump = context.HARLog.json() - compressed_json_dump = context.HARLog.compress() - - if context.dump_file == '-': - mitmproxy.ctx.log(pprint.pformat(json.loads(json_dump))) - elif context.dump_file.endswith('.zhar'): - with open(context.dump_file, "wb") as f: - f.write(compressed_json_dump) - else: - with open(context.dump_file, "wb") as f: - f.write(json_dump) - mitmproxy.ctx.log( - "HAR log finished with %s bytes (%s bytes compressed)" % ( - len(json_dump), len(compressed_json_dump) - ) - ) - mitmproxy.ctx.log( - "Compression rate is %s%%" % str( - 100. * len(compressed_json_dump) / len(json_dump) - ) - ) - - -def format_cookies(obj): - if obj: - return [{"name": k.strip(), "value": v[0]} for k, v in obj.items()] - return "" - - -def format_headers(obj): - if obj: - return [{"name": k, "value": v} for k, v in obj.fields] - return "" - - -def print_attributes(obj, filter_string=None, hide_privates=False): - """ - Useful helper method to quickly get all attributes of an object and its - values. - """ - for attr in dir(obj): - if hide_privates and "__" in attr: - continue - if filter_string is not None and filter_string not in attr: - continue - value = getattr(obj, attr) - print("%s.%s" % ('obj', attr), value, type(value)) diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py index 36594bcd..8cde1bfd 100644 --- a/examples/redirect_requests.py +++ b/examples/redirect_requests.py @@ -2,7 +2,6 @@ This example shows two ways to redirect flows to other destinations. """ from mitmproxy.models import HTTPResponse -from netlib.http import Headers def request(flow): @@ -12,11 +11,7 @@ def request(flow): # Method 1: Answer with a locally generated response if flow.request.pretty_host.endswith("example.com"): - resp = HTTPResponse( - b"HTTP/1.1", 200, b"OK", - Headers(Content_Type="text/html"), - b"helloworld" - ) + resp = HTTPResponse.make(200, b"Hello World", {"Content-Type": "text/html"}) flow.reply.send(resp) # Method 2: Redirect the request to a different server diff --git a/mitmproxy/console/common.py b/mitmproxy/console/common.py index 5a24e789..25181cb7 100644 --- a/mitmproxy/console/common.py +++ b/mitmproxy/console/common.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from __future__ import absolute_import, print_function, division import os @@ -328,11 +330,11 @@ def export_to_clip_or_file(key, scope, flow, writer): flowcache = utils.LRUCache(800) -def raw_format_flow(f, focus, extended): +def raw_format_flow(f): f = dict(f) pile = [] req = [] - if extended: + if f["extended"]: req.append( fcol( human.format_timestamp(f["req_timestamp"]), @@ -340,7 +342,7 @@ def raw_format_flow(f, focus, extended): ) ) else: - req.append(fcol(">>" if focus else " ", "focus")) + req.append(fcol(">>" if f["focus"] else " ", "focus")) if f["marked"]: req.append(fcol(SYMBOL_MARK, "mark")) @@ -359,6 +361,10 @@ def raw_format_flow(f, focus, extended): uc = "title" url = f["req_url"] + + if f["max_url_len"] and len(url) > f["max_url_len"]: + url = url[:f["max_url_len"]] + "…" + if f["req_http_version"] not in ("HTTP/1.0", "HTTP/1.1"): url += " " + f["req_http_version"] req.append( @@ -384,7 +390,7 @@ def raw_format_flow(f, focus, extended): if f["resp_is_replay"]: resp.append(fcol(SYMBOL_REPLAY, "replay")) resp.append(fcol(f["resp_code"], ccol)) - if extended: + if f["extended"]: resp.append(fcol(f["resp_reason"], ccol)) if f["intercepted"] and f["resp_code"] and not f["acked"]: rc = "intercept" @@ -410,8 +416,12 @@ def raw_format_flow(f, focus, extended): return urwid.Pile(pile) -def format_flow(f, focus, extended=False, hostheader=False): +def format_flow(f, focus, extended=False, hostheader=False, max_url_len=False): d = dict( + focus=focus, + extended=extended, + max_url_len=max_url_len, + intercepted = f.intercepted, acked = f.reply.state == "committed", @@ -449,7 +459,5 @@ def format_flow(f, focus, extended=False, hostheader=False): d["resp_ctype"] = t.split(";")[0] else: d["resp_ctype"] = "" - return flowcache.get( - raw_format_flow, - tuple(sorted(d.items())), focus, extended - ) + + return flowcache.get(raw_format_flow, tuple(sorted(d.items()))) diff --git a/mitmproxy/console/flowlist.py b/mitmproxy/console/flowlist.py index 7e69e098..11e8fc99 100644 --- a/mitmproxy/console/flowlist.py +++ b/mitmproxy/console/flowlist.py @@ -116,10 +116,12 @@ class ConnectionItem(urwid.WidgetWrap): urwid.WidgetWrap.__init__(self, w) def get_text(self): + cols, _ = self.master.ui.get_cols_rows() return common.format_flow( self.flow, self.f, - hostheader = self.master.options.showhost, + hostheader=self.master.options.showhost, + max_url_len=cols, ) def selectable(self): diff --git a/mitmproxy/console/flowview.py b/mitmproxy/console/flowview.py index 6d74be65..15be379b 100644 --- a/mitmproxy/console/flowview.py +++ b/mitmproxy/console/flowview.py @@ -374,10 +374,7 @@ class FlowView(tabs.Tabs): message = self.flow.request else: if not self.flow.response: - self.flow.response = models.HTTPResponse( - self.flow.request.http_version, - 200, b"OK", Headers(), b"" - ) + self.flow.response = models.HTTPResponse.make(200, b"") message = self.flow.response self.flow.backup() diff --git a/mitmproxy/console/grideditor/editors.py b/mitmproxy/console/grideditor/editors.py index 80f0541b..a17fd766 100644 --- a/mitmproxy/console/grideditor/editors.py +++ b/mitmproxy/console/grideditor/editors.py @@ -203,6 +203,9 @@ class CookieAttributeEditor(base.GridEditor): col_text.Column("Value"), ] + def data_in(self, data): + return [(k, v or "") for k, v in data] + def data_out(self, data): ret = [] for i in data: diff --git a/mitmproxy/console/window.py b/mitmproxy/console/window.py index b24718be..35593643 100644 --- a/mitmproxy/console/window.py +++ b/mitmproxy/console/window.py @@ -27,7 +27,7 @@ class Window(urwid.Frame): if not k: if args[1] == "mouse drag": signals.status_message.send( - message = "Hold down shift, alt or ctrl to select text.", + message = "Hold down fn, shift, alt or ctrl to select text or use the --no-mouse parameter.", expire = 1 ) elif args[1] == "mouse press" and args[2] == 4: diff --git a/mitmproxy/controller.py b/mitmproxy/controller.py index 72374f31..d886af97 100644 --- a/mitmproxy/controller.py +++ b/mitmproxy/controller.py @@ -28,6 +28,8 @@ Events = frozenset([ "response", "responseheaders", + "websockets_handshake", + "next_layer", "error", @@ -275,10 +277,6 @@ class Reply(object): def has_message(self): return self.value != NO_REPLY - @property - def done(self): - return self.state == "committed" - def handle(self): """ Reply are handled by controller.handlers, which may be nested. The first handler takes diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py index 0475ef4e..9cdcc8dd 100644 --- a/mitmproxy/flow/master.py +++ b/mitmproxy/flow/master.py @@ -334,6 +334,10 @@ class FlowMaster(controller.Master): self.client_playback.clear(f) return f + @controller.handler + def websockets_handshake(self, f): + return f + def handle_intercept(self, f): self.state.update_flow(f) diff --git a/mitmproxy/protocol/__init__.py b/mitmproxy/protocol/__init__.py index 510cd195..b99b55bd 100644 --- a/mitmproxy/protocol/__init__.py +++ b/mitmproxy/protocol/__init__.py @@ -29,8 +29,10 @@ from __future__ import absolute_import, print_function, division from .base import Layer, ServerConnectionMixin from .http import UpstreamConnectLayer +from .http import HttpLayer from .http1 import Http1Layer from .http2 import Http2Layer +from .websockets import WebSocketsLayer from .rawtcp import RawTCPLayer from .tls import TlsClientHello from .tls import TlsLayer @@ -40,7 +42,9 @@ __all__ = [ "Layer", "ServerConnectionMixin", "TlsLayer", "is_tls_record_magic", "TlsClientHello", "UpstreamConnectLayer", + "HttpLayer", "Http1Layer", "Http2Layer", + "WebSocketsLayer", "RawTCPLayer", ] diff --git a/mitmproxy/protocol/http.py b/mitmproxy/protocol/http.py index 2c70f288..1418d6e9 100644 --- a/mitmproxy/protocol/http.py +++ b/mitmproxy/protocol/http.py @@ -7,12 +7,14 @@ import traceback import h2.exceptions import six -import netlib.exceptions from mitmproxy import exceptions from mitmproxy import models from mitmproxy.protocol import base + +import netlib.exceptions from netlib import http from netlib import tcp +from netlib import websockets class _HttpTransmissionLayer(base.Layer): @@ -132,6 +134,8 @@ class HttpLayer(base.Layer): # We cannot rely on server_conn.tls_established, # see https://github.com/mitmproxy/mitmproxy/issues/925 self.__initial_server_tls = None + # Requests happening after CONNECT do not need Proxy-Authorization headers. + self.http_authenticated = False def __call__(self): if self.mode == "transparent": @@ -140,41 +144,64 @@ class HttpLayer(base.Layer): while True: try: request = self.get_request_from_client() - self.log("request", "debug", [repr(request)]) - - # Handle Proxy Authentication - # Proxy Authentication conceptually does not work in transparent mode. - # We catch this misconfiguration on startup. Here, we sort out requests - # after a successful CONNECT request (which do not need to be validated anymore) - if self.mode != "transparent" and not self.authenticate(request): - return - # Make sure that the incoming request matches our expectations self.validate_request(request) + except netlib.exceptions.HttpReadDisconnect: + # don't throw an error for disconnects that happen before/between requests. + return + except netlib.exceptions.HttpException as e: + # We optimistically guess there might be an HTTP client on the + # other end + self.send_error_response(400, repr(e)) + self.log( + "request", + "warn", + "HTTP protocol error in client request: %s" % e + ) + return + + self.log("request", "debug", [repr(request)]) + # Handle Proxy Authentication + # Proxy Authentication conceptually does not work in transparent mode. + # We catch this misconfiguration on startup. Here, we sort out requests + # after a successful CONNECT request (which do not need to be validated anymore) + if not (self.http_authenticated or self.authenticate(request)): + return + + flow = models.HTTPFlow(self.client_conn, self.server_conn, live=self) + flow.request = request + + try: # Regular Proxy Mode: Handle CONNECT if self.mode == "regular" and request.first_line_format == "authority": self.handle_regular_mode_connect(request) return - - except netlib.exceptions.HttpReadDisconnect: - # don't throw an error for disconnects that happen before/between requests. + except (exceptions.ProtocolException, netlib.exceptions.NetlibException) as e: + # HTTPS tasting means that ordinary errors like resolution and + # connection errors can happen here. + self.send_error_response(502, repr(e)) + flow.error = models.Error(str(e)) + self.channel.ask("error", flow) return - except netlib.exceptions.NetlibException as e: - self.send_error_response(400, repr(e)) - six.reraise(exceptions.ProtocolException, exceptions.ProtocolException( - "Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2]) + + # set upstream auth + if self.mode == "upstream" and self.config.upstream_auth is not None: + flow.request.headers["Proxy-Authorization"] = self.config.upstream_auth + self.process_request_hook(flow) try: - flow = models.HTTPFlow(self.client_conn, self.server_conn, live=self) - flow.request = request - # set upstream auth - if self.mode == "upstream" and self.config.upstream_auth is not None: - flow.request.headers["Proxy-Authorization"] = self.config.upstream_auth - self.process_request_hook(flow) + if websockets.check_handshake(request.headers) and websockets.check_client_version(request.headers): + # we only support RFC6455 with WebSockets version 13 + # allow inline scripts to manupulate the client handshake + self.channel.ask("websockets_handshake", flow) if not flow.response: - self.establish_server_connection(flow) + self.establish_server_connection( + flow.request.host, + flow.request.port, + flow.request.scheme + ) self.get_response_from_server(flow) else: # response was set by an inline script. @@ -192,7 +219,7 @@ class HttpLayer(base.Layer): # It may be useful to pass additional args (such as the upgrade header) # to next_layer in the future if flow.response.status_code == 101: - layer = self.ctx.next_layer(self) + layer = self.ctx.next_layer(self, flow) layer() return @@ -203,11 +230,9 @@ class HttpLayer(base.Layer): except (exceptions.ProtocolException, netlib.exceptions.NetlibException) as e: self.send_error_response(502, repr(e)) - if not flow.response: flow.error = models.Error(str(e)) self.channel.ask("error", flow) - self.log(traceback.format_exc(), "debug") return else: six.reraise(exceptions.ProtocolException, exceptions.ProtocolException( @@ -225,9 +250,9 @@ class HttpLayer(base.Layer): request.body = b"".join(self.read_request_body(request)) return request - def send_error_response(self, code, message): + def send_error_response(self, code, message, headers=None): try: - response = models.make_error_response(code, message) + response = models.make_error_response(code, message, headers) self.send_response(response) except (netlib.exceptions.NetlibException, h2.exceptions.H2Error, exceptions.Http2ProtocolException): self.log(traceback.format_exc(), "debug") @@ -239,6 +264,7 @@ class HttpLayer(base.Layer): return self.set_server(address) def handle_regular_mode_connect(self, request): + self.http_authenticated = True self.set_server((request.host, request.port)) self.send_response(models.make_connect_response(request.data.http_version)) layer = self.ctx.next_layer(self) @@ -340,9 +366,9 @@ class HttpLayer(base.Layer): flow.response = request_reply return - def establish_server_connection(self, flow): - address = tcp.Address((flow.request.host, flow.request.port)) - tls = (flow.request.scheme == "https") + def establish_server_connection(self, host, port, scheme): + address = tcp.Address((host, port)) + tls = (scheme == "https") if self.mode == "regular" or self.mode == "transparent": # If there's an existing connection that doesn't match our expectations, kill it. @@ -397,10 +423,17 @@ class HttpLayer(base.Layer): if self.config.authenticator.authenticate(request.headers): self.config.authenticator.clean(request.headers) else: - self.send_response(models.make_error_response( - 407, - "Proxy Authentication Required", - http.Headers(**self.config.authenticator.auth_challenge_headers()) - )) + if self.mode == "transparent": + self.send_response(models.make_error_response( + 401, + "Authentication Required", + http.Headers(**self.config.authenticator.auth_challenge_headers()) + )) + else: + self.send_response(models.make_error_response( + 407, + "Proxy Authentication Required", + http.Headers(**self.config.authenticator.auth_challenge_headers()) + )) return False return True diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index eb5586cb..0e42d619 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, print_function, division import threading import time import traceback +import functools import h2.exceptions import six @@ -54,21 +55,18 @@ class SafeH2Connection(connection.H2Connection): self.update_settings(new_settings) self.conn.send(self.data_to_send()) - def safe_send_headers(self, is_zombie, stream_id, headers, **kwargs): + def safe_send_headers(self, raise_zombie, stream_id, headers, **kwargs): with self.lock: - if is_zombie(): # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + raise_zombie() self.send_headers(stream_id, headers.fields, **kwargs) self.conn.send(self.data_to_send()) - def safe_send_body(self, is_zombie, stream_id, chunks): + def safe_send_body(self, raise_zombie, stream_id, chunks): for chunk in chunks: position = 0 while position < len(chunk): self.lock.acquire() - if is_zombie(): # pragma: no cover - self.lock.release() - raise exceptions.Http2ProtocolException("Zombie Stream") + raise_zombie(self.lock.release) max_outbound_frame_size = self.max_outbound_frame_size frame_chunk = chunk[position:position + max_outbound_frame_size] if self.local_flow_control_window(stream_id) < len(frame_chunk): @@ -84,8 +82,7 @@ class SafeH2Connection(connection.H2Connection): self.lock.release() position += max_outbound_frame_size with self.lock: - if is_zombie(): # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + raise_zombie() self.end_stream(stream_id) self.conn.send(self.data_to_send()) @@ -344,6 +341,17 @@ class Http2Layer(base.Layer): self._kill_all_streams() +def detect_zombie_stream(func): + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + self.raise_zombie() + result = func(self, *args, **kwargs) + self.raise_zombie() + return result + + return wrapper + + class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread): def __init__(self, ctx, stream_id, request_headers): @@ -412,15 +420,16 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) def queued_data_length(self, v): self.request_queued_data_length = v - def is_zombie(self): - return self.zombie is not None + def raise_zombie(self, pre_command=None): + if self.zombie is not None: + if pre_command is not None: + pre_command() + raise exceptions.Http2ProtocolException("Zombie Stream") + @detect_zombie_stream def read_request(self): self.request_data_finished.wait() - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") - data = [] while self.request_data_queue.qsize() > 0: data.append(self.request_data_queue.get()) @@ -445,15 +454,14 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) def read_request_body(self, request): # pragma: no cover raise NotImplementedError() + @detect_zombie_stream def send_request(self, message): if self.pushed: # nothing to do here return while True: - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") - + self.raise_zombie() self.server_conn.h2.lock.acquire() max_streams = self.server_conn.h2.remote_settings.max_concurrent_streams @@ -467,8 +475,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) break # We must not assign a stream id if we are already a zombie. - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + self.raise_zombie() self.server_stream_id = self.server_conn.h2.get_next_available_stream_id() self.server_to_client_stream_ids[self.server_stream_id] = self.client_stream_id @@ -490,7 +497,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) try: self.server_conn.h2.safe_send_headers( - self.is_zombie, + self.raise_zombie, self.server_stream_id, headers, end_stream=self.no_body, @@ -505,19 +512,16 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) if not self.no_body: self.server_conn.h2.safe_send_body( - self.is_zombie, + self.raise_zombie, self.server_stream_id, [message.body] ) - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") - + @detect_zombie_stream def read_response_headers(self): self.response_arrived.wait() - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + self.raise_zombie() status_code = int(self.response_headers.get(':status', 502)) headers = self.response_headers.copy() @@ -533,6 +537,7 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) timestamp_end=self.timestamp_end, ) + @detect_zombie_stream def read_response_body(self, request, response): while True: try: @@ -540,14 +545,13 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) except queue.Empty: # pragma: no cover pass if self.response_data_finished.is_set(): - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + self.raise_zombie() while self.response_data_queue.qsize() > 0: yield self.response_data_queue.get() break - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + self.raise_zombie() + @detect_zombie_stream def send_response_headers(self, response): headers = response.headers.copy() headers.insert(0, ":status", str(response.status_code)) @@ -556,21 +560,21 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) del headers[forbidden_header] with self.client_conn.h2.lock: self.client_conn.h2.safe_send_headers( - self.is_zombie, + self.raise_zombie, self.client_stream_id, headers ) - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + @detect_zombie_stream def send_response_body(self, _response, chunks): self.client_conn.h2.safe_send_body( - self.is_zombie, + self.raise_zombie, self.client_stream_id, chunks ) - if self.zombie: # pragma: no cover - raise exceptions.Http2ProtocolException("Zombie Stream") + + def __call__(self): + raise EnvironmentError('Http2SingleStreamLayer must be run as thread') def run(self): layer = http.HttpLayer(self, self.mode) diff --git a/mitmproxy/protocol/tls.py b/mitmproxy/protocol/tls.py index d08e2e32..e41a9af0 100644 --- a/mitmproxy/protocol/tls.py +++ b/mitmproxy/protocol/tls.py @@ -369,8 +369,10 @@ class TlsLayer(base.Layer): not self.config.options.no_upstream_cert and ( self.config.options.add_upstream_certs_to_client_chain or - self._client_hello.alpn_protocols or - not self._client_hello.sni + self._client_tls and ( + self._client_hello.alpn_protocols or + not self._client_hello.sni + ) ) ) establish_server_tls_now = ( @@ -434,7 +436,7 @@ class TlsLayer(base.Layer): if self._custom_server_sni is False: return None else: - return self._custom_server_sni or self._client_hello.sni + return self._custom_server_sni or self._client_hello and self._client_hello.sni @property def alpn_for_client_connection(self): @@ -509,21 +511,18 @@ class TlsLayer(base.Layer): def _establish_tls_with_server(self): self.log("Establish TLS with server", "debug") try: - # We only support http/1.1 and h2. - # If the server only supports spdy (next to http/1.1), it may select that - # and mitmproxy would enter TCP passthrough mode, which we want to avoid. - def deprecated_http2_variant(x): - return x.startswith(b"h2-") or x.startswith(b"spdy") - - if self._client_hello.alpn_protocols: - alpn = [x for x in self._client_hello.alpn_protocols if not deprecated_http2_variant(x)] - else: - alpn = None - if alpn and b"h2" in alpn and not self.config.options.http2: - alpn.remove(b"h2") + alpn = None + if self._client_tls: + if self._client_hello.alpn_protocols: + # We only support http/1.1 and h2. + # If the server only supports spdy (next to http/1.1), it may select that + # and mitmproxy would enter TCP passthrough mode, which we want to avoid. + alpn = [x for x in self._client_hello.alpn_protocols if not (x.startswith(b"h2-") or x.startswith(b"spdy"))] + if alpn and b"h2" in alpn and not self.config.options.http2: + alpn.remove(b"h2") ciphers_server = self.config.options.ciphers_server - if not ciphers_server: + if not ciphers_server and self._client_tls: ciphers_server = [] for id in self._client_hello.cipher_suites: if id in CIPHER_ID_NAME_MAP.keys(): @@ -562,7 +561,8 @@ class TlsLayer(base.Layer): sys.exc_info()[2] ) - self.log("ALPN selected by server: %s" % self.alpn_for_client_connection, "debug") + proto = self.alpn_for_client_connection.decode() if self.alpn_for_client_connection else '-' + self.log("ALPN selected by server: {}".format(proto), "debug") def _find_cert(self): """ diff --git a/mitmproxy/protocol/websockets.py b/mitmproxy/protocol/websockets.py new file mode 100644 index 00000000..f15a38ef --- /dev/null +++ b/mitmproxy/protocol/websockets.py @@ -0,0 +1,108 @@ +from __future__ import absolute_import, print_function, division + +import socket +import struct + +from OpenSSL import SSL + +from mitmproxy import exceptions +from mitmproxy.protocol import base + +import netlib.exceptions +from netlib import tcp +from netlib import websockets + + +class WebSocketsLayer(base.Layer): + """ + WebSockets layer to intercept, modify, and forward WebSockets connections + + Only version 13 is supported (as specified in RFC6455) + Only HTTP/1.1-initiated connections are supported. + + The client starts by sending an Upgrade-request. + In order to determine the handshake and negotiate the correct protocol + and extensions, the Upgrade-request is forwarded to the server. + The response from the server is then parsed and negotiated settings are extracted. + Finally the handshake is completed by forwarding the server-response to the client. + After that, only WebSockets frames are exchanged. + + PING/PONG frames pass through and must be answered by the other endpoint. + + CLOSE frames are forwarded before this WebSocketsLayer terminates. + + This layer is transparent to any negotiated extensions. + This layer is transparent to any negotiated subprotocols. + Only raw frames are forwarded to the other endpoint. + """ + + def __init__(self, ctx, flow): + super(WebSocketsLayer, self).__init__(ctx) + self._flow = flow + + self.client_key = websockets.get_client_key(self._flow.request.headers) + self.client_protocol = websockets.get_protocol(self._flow.request.headers) + self.client_extensions = websockets.get_extensions(self._flow.request.headers) + + self.server_accept = websockets.get_server_accept(self._flow.response.headers) + self.server_protocol = websockets.get_protocol(self._flow.response.headers) + self.server_extensions = websockets.get_extensions(self._flow.response.headers) + + def _handle_frame(self, frame, source_conn, other_conn, is_server): + self.log( + "WebSockets Frame received from {}".format("server" if is_server else "client"), + "debug", + [repr(frame)] + ) + + if frame.header.opcode & 0x8 == 0: + # forward the data frame to the other side + other_conn.send(bytes(frame)) + self.log("WebSockets frame received by {}: {}".format(is_server, frame), "debug") + elif frame.header.opcode in (websockets.OPCODE.PING, websockets.OPCODE.PONG): + # just forward the ping/pong to the other side + other_conn.send(bytes(frame)) + elif frame.header.opcode == websockets.OPCODE.CLOSE: + other_conn.send(bytes(frame)) + + code = '(status code missing)' + msg = None + reason = '(message missing)' + if len(frame.payload) >= 2: + code, = struct.unpack('!H', frame.payload[:2]) + msg = websockets.CLOSE_REASON.get_name(code, default='unknown status code') + if len(frame.payload) > 2: + reason = frame.payload[2:] + self.log("WebSockets connection closed: {} {}, {}".format(code, msg, reason), "info") + + # close the connection + return False + else: + # unknown frame - just forward it + other_conn.send(bytes(frame)) + + # continue the connection + return True + + def __call__(self): + client = self.client_conn.connection + server = self.server_conn.connection + conns = [client, server] + + try: + while not self.channel.should_exit.is_set(): + r = tcp.ssl_read_select(conns, 1) + for conn in r: + source_conn = self.client_conn if conn == client else self.server_conn + other_conn = self.server_conn if conn == client else self.client_conn + is_server = (conn == self.server_conn.connection) + + frame = websockets.Frame.from_file(source_conn.rfile) + + if not self._handle_frame(frame, source_conn, other_conn, is_server): + return + except (socket.error, netlib.exceptions.TcpException, SSL.Error) as e: + self.log("WebSockets connection closed unexpectedly by {}: {}".format( + "server" if is_server else "client", repr(e)), "info") + except Exception as e: # pragma: no cover + raise exceptions.ProtocolException("Error in WebSockets connection: {}".format(repr(e))) diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index cf75830a..2cf8410a 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -179,7 +179,13 @@ class ProxyConfig: ) except ValueError as v: raise exceptions.OptionsError(str(v)) - self.authenticator = authentication.BasicProxyAuth( - password_manager, - "mitmproxy" - ) + if options.mode == "reverse": + self.authenticator = authentication.BasicWebsiteAuth( + password_manager, + self.upstream_server.address + ) + else: + self.authenticator = authentication.BasicProxyAuth( + password_manager, + "mitmproxy" + ) diff --git a/mitmproxy/proxy/root_context.py b/mitmproxy/proxy/root_context.py index 81dd625c..95611362 100644 --- a/mitmproxy/proxy/root_context.py +++ b/mitmproxy/proxy/root_context.py @@ -4,6 +4,7 @@ import sys import six +from netlib import websockets import netlib.exceptions from mitmproxy import exceptions from mitmproxy import protocol @@ -32,7 +33,7 @@ class RootContext(object): self.channel = channel self.config = config - def next_layer(self, top_layer): + def next_layer(self, top_layer, flow=None): """ This function determines the next layer in the protocol stack. @@ -42,10 +43,22 @@ class RootContext(object): Returns: The next layer """ - layer = self._next_layer(top_layer) + layer = self._next_layer(top_layer, flow) return self.channel.ask("next_layer", layer) - def _next_layer(self, top_layer): + def _next_layer(self, top_layer, flow): + if flow is not None: + # We already have a flow, try to derive the next information from it + + # Check for WebSockets handshake + is_websockets = ( + flow and + websockets.check_handshake(flow.request.headers) and + websockets.check_handshake(flow.response.headers) + ) + if isinstance(top_layer, protocol.HttpLayer) and is_websockets: + return protocol.WebSocketsLayer(top_layer, flow) + try: d = top_layer.client_conn.rfile.peek(3) except netlib.exceptions.TcpException as e: diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 23946cd2..c92ba4d3 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -243,7 +243,6 @@ class FlowHandler(RequestHandler): flow = self.flow flow.backup() for a, b in six.iteritems(self.json): - if a == "request": request = flow.request for k, v in six.iteritems(b): @@ -260,7 +259,6 @@ class FlowHandler(RequestHandler): elif a == "response": response = flow.response - for k, v in six.iteritems(b): if k == "msg": response.msg = str(v) diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index d376b5e3..9ff76104 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -185,7 +185,7 @@ var Query = exports.Query = { SHOW_EVENTLOG: "e" }; -},{"./dispatcher.js":46}],3:[function(require,module,exports){ +},{"./dispatcher.js":48}],3:[function(require,module,exports){ (function (process){ 'use strict'; @@ -203,16 +203,10 @@ var _reduxThunk = require('redux-thunk'); var _reduxThunk2 = _interopRequireDefault(_reduxThunk); -var _reactRouter = require('react-router'); - var _ProxyApp = require('./components/ProxyApp'); var _ProxyApp2 = _interopRequireDefault(_ProxyApp); -var _MainView = require('./components/MainView'); - -var _MainView2 = _interopRequireDefault(_MainView); - var _index = require('./ducks/index'); var _index2 = _interopRequireDefault(_index); @@ -241,23 +235,13 @@ document.addEventListener('DOMContentLoaded', function () { (0, _reactDom.render)(_react2.default.createElement( _reactRedux.Provider, { store: store }, - _react2.default.createElement( - _reactRouter.Router, - { history: _reactRouter.hashHistory }, - _react2.default.createElement(_reactRouter.Redirect, { from: '/', to: '/flows' }), - _react2.default.createElement( - _reactRouter.Route, - { path: '/', component: _ProxyApp2.default }, - _react2.default.createElement(_reactRouter.Route, { path: 'flows', component: _MainView2.default }), - _react2.default.createElement(_reactRouter.Route, { path: 'flows/:flowId/:detailTab', component: _MainView2.default }) - ) - ) + _react2.default.createElement(_ProxyApp2.default, null) ), document.getElementById("mitmproxy")); }); }).call(this,require('_process')) -},{"./components/MainView":35,"./components/ProxyApp":37,"./ducks/eventLog":48,"./ducks/index":51,"_process":1,"react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router","redux":"redux","redux-logger":"redux-logger","redux-thunk":"redux-thunk"}],4:[function(require,module,exports){ +},{"./components/ProxyApp":37,"./ducks/eventLog":50,"./ducks/index":53,"_process":1,"react":"react","react-dom":"react-dom","react-redux":"react-redux","redux":"redux","redux-logger":"redux-logger","redux-thunk":"redux-thunk"}],4:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -343,7 +327,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { updateEdit: _flow.updateEdit })(ContentView); -},{"../ducks/ui/flow":54,"./ContentView/ContentViews":8,"./ContentView/MetaViews":10,"./ContentView/ShowFullContentButton":11,"react":"react","react-redux":"react-redux"}],5:[function(require,module,exports){ +},{"../ducks/ui/flow":56,"./ContentView/ContentViews":8,"./ContentView/MetaViews":10,"./ContentView/ShowFullContentButton":11,"react":"react","react-redux":"react-redux"}],5:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -355,8 +339,6 @@ var _react = require('react'); var _react2 = _interopRequireDefault(_react); -var _reactDom = require('react-dom'); - var _reactCodemirror = require('react-codemirror'); var _reactCodemirror2 = _interopRequireDefault(_reactCodemirror); @@ -385,7 +367,7 @@ function CodeEditor(_ref) { ); } -},{"react":"react","react-codemirror":"react-codemirror","react-dom":"react-dom"}],6:[function(require,module,exports){ +},{"react":"react","react-codemirror":"react-codemirror"}],6:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -514,7 +496,7 @@ exports.default = function (View) { }), _temp; }; -},{"../../flow/utils.js":62,"react":"react"}],7:[function(require,module,exports){ +},{"../../flow/utils.js":64,"react":"react"}],7:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -660,7 +642,9 @@ var ViewServer = function (_Component) { }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { - this.setContentView(nextProps); + if (nextProps.content != this.props.content) { + this.setContentView(nextProps); + } } }, { key: 'setContentView', @@ -672,9 +656,7 @@ var ViewServer = function (_Component) { } props.setContentViewDescription(props.contentView != this.data.description ? this.data.description : ''); - - var isFullContentShown = this.data.lines.length < props.maxLines; - if (isFullContentShown) props.setShowFullContent(true); + props.setContent(this.data.lines); } }, { key: 'render', @@ -696,11 +678,13 @@ var ViewServer = function (_Component) { return _react2.default.createElement( 'div', { key: 'line' + i }, - line.map(function (tuple, j) { + line.map(function (element, j) { + var style = void 0, + text = element; return _react2.default.createElement( 'span', - { key: 'tuple' + j, className: tuple[0] }, - tuple[1] + { key: 'tuple' + j, className: style }, + element ); }) ); @@ -714,25 +698,29 @@ var ViewServer = function (_Component) { return ViewServer; }(_react.Component); -ViewServer.defaultProps = { - maxLines: 80 +ViewServer.propTypes = { + showFullContent: _react.PropTypes.bool.isRequired, + maxLines: _react.PropTypes.number.isRequired, + setContentViewDescription: _react.PropTypes.func.isRequired, + setContent: _react.PropTypes.func.isRequired }; exports.ViewServer = ViewServer = (0, _reactRedux.connect)(function (state) { return { - showFullContent: state.ui.flow.showFullContent + showFullContent: state.ui.flow.showFullContent, + maxLines: state.ui.flow.maxContentLines }; }, { setContentViewDescription: _flow.setContentViewDescription, - setShowFullContent: _flow.setShowFullContent + setContent: _flow.setContent })((0, _ContentLoader2.default)(ViewServer)); exports.Edit = Edit; exports.ViewServer = ViewServer; exports.ViewImage = ViewImage; -},{"../../ducks/ui/flow":54,"../../flow/utils":62,"./CodeEditor":5,"./ContentLoader":6,"react":"react","react-redux":"react-redux"}],9:[function(require,module,exports){ +},{"../../ducks/ui/flow":56,"../../flow/utils":64,"./CodeEditor":5,"./ContentLoader":6,"react":"react","react-redux":"react-redux"}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -763,7 +751,7 @@ function DownloadContentButton(_ref) { ); } -},{"../../flow/utils":62,"react":"react"}],10:[function(require,module,exports){ +},{"../../flow/utils":64,"react":"react"}],10:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -844,7 +832,7 @@ function ContentTooLarge(_ref3) { ); } -},{"../../utils.js":63,"./DownloadContentButton":9,"./UploadContentButton":12,"react":"react"}],11:[function(require,module,exports){ +},{"../../utils.js":65,"./DownloadContentButton":9,"./UploadContentButton":12,"react":"react"}],11:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -875,30 +863,54 @@ ShowFullContentButton.propTypes = { function ShowFullContentButton(_ref) { var setShowFullContent = _ref.setShowFullContent; var showFullContent = _ref.showFullContent; + var visibleLines = _ref.visibleLines; + var contentLines = _ref.contentLines; - return !showFullContent && _react2.default.createElement(_Button2.default, { className: 'view-all-content-btn btn-xs', onClick: function onClick() { - return setShowFullContent(true); - }, text: 'Show full content' }); + return !showFullContent && _react2.default.createElement( + 'div', + null, + _react2.default.createElement(_Button2.default, { className: 'view-all-content-btn btn-xs', onClick: function onClick() { + return setShowFullContent(true); + }, text: 'Show full content' }), + _react2.default.createElement( + 'span', + { className: 'pull-right' }, + ' ', + visibleLines, + '/', + contentLines, + ' are visible  ' + ) + ); } exports.default = (0, _reactRedux.connect)(function (state) { return { - showFullContent: state.ui.flow.showFullContent + showFullContent: state.ui.flow.showFullContent, + visibleLines: state.ui.flow.maxContentLines, + contentLines: state.ui.flow.content.length + }; }, { setShowFullContent: _flow.setShowFullContent })(ShowFullContentButton); -},{"../../ducks/ui/flow":54,"../common/Button":40,"react":"react","react-dom":"react-dom","react-redux":"react-redux"}],12:[function(require,module,exports){ -"use strict"; +},{"../../ducks/ui/flow":56,"../common/Button":40,"react":"react","react-dom":"react-dom","react-redux":"react-redux"}],12:[function(require,module,exports){ +'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = UploadContentButton; -var _react = require("react"); +var _react = require('react'); + +var _FileChooser = require('../common/FileChooser'); + +var _FileChooser2 = _interopRequireDefault(_FileChooser); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } UploadContentButton.propTypes = { uploadContent: _react.PropTypes.func.isRequired @@ -910,44 +922,24 @@ function UploadContentButton(_ref) { var fileInput = void 0; - return React.createElement( - "a", - { className: "btn btn-default btn-xs", - onClick: function onClick() { - return fileInput.click(); - }, - title: "Upload a file to replace the content." }, - React.createElement("i", { className: "fa fa-upload" }), - React.createElement("input", { - ref: function ref(_ref2) { - return fileInput = _ref2; - }, - className: "hidden", - type: "file", - onChange: function onChange(e) { - if (e.target.files.length > 0) uploadContent(e.target.files[0]); - } - }) - ); + return React.createElement(_FileChooser2.default, { + icon: 'fa-upload', + title: 'Upload a file to replace the content.', + onOpenFile: uploadContent, + className: 'btn btn-default btn-xs' }); } -},{"react":"react"}],13:[function(require,module,exports){ +},{"../common/FileChooser":42,"react":"react"}],13:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - var _react = require('react'); var _react2 = _interopRequireDefault(_react); -var _classnames = require('classnames'); - -var _classnames2 = _interopRequireDefault(_classnames); - var _reactRedux = require('react-redux'); var _ContentViews = require('./ContentViews'); @@ -956,123 +948,65 @@ var ContentViews = _interopRequireWildcard(_ContentViews); var _flow = require('../../ducks/ui/flow'); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } +var _Dropdown = require('../common/Dropdown'); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var _Dropdown2 = _interopRequireDefault(_Dropdown); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } +ViewSelector.propTypes = { + contentViews: _react.PropTypes.array.isRequired, + activeView: _react.PropTypes.string.isRequired, + isEdit: _react.PropTypes.bool.isRequired, + setContentView: _react.PropTypes.func.isRequired +}; -function ViewItem(_ref) { - var name = _ref.name; +function ViewSelector(_ref) { + var contentViews = _ref.contentViews; + var activeView = _ref.activeView; + var isEdit = _ref.isEdit; var setContentView = _ref.setContentView; - var children = _ref.children; - return _react2.default.createElement( - 'li', + var edit = ContentViews.Edit.displayName; + var inner = _react2.default.createElement( + 'span', null, + ' ', _react2.default.createElement( + 'b', + null, + 'View:' + ), + ' ', + activeView, + _react2.default.createElement('span', { className: 'caret' }), + ' ' + ); + + return _react2.default.createElement( + _Dropdown2.default, + { dropup: true, className: 'pull-left', btnClass: 'btn btn-default btn-xs', text: inner }, + contentViews.map(function (name) { + return _react2.default.createElement( + 'a', + { href: '#', key: name, onClick: function onClick(e) { + e.preventDefault();setContentView(name); + } }, + name.toLowerCase().replace('_', ' ') + ); + }), + isEdit && _react2.default.createElement( 'a', - { href: '#', onClick: function onClick() { - return setContentView(name); + { href: '#', onClick: function onClick(e) { + e.preventDefault();setContentView(edit); } }, - children + edit.toLowerCase() ) ); } -/*ViewSelector.propTypes = { - contentViews: PropTypes.array.isRequired, - activeView: PropTypes.string.isRequired, - isEdit: PropTypes.bool.isRequired, - isContentViewSelectorOpen: PropTypes.bool.isRequired, - setContentViewSelectorOpen: PropTypes.func.isRequired -}*/ - -var ViewSelector = function (_Component) { - _inherits(ViewSelector, _Component); - - function ViewSelector(props, context) { - _classCallCheck(this, ViewSelector); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ViewSelector).call(this, props, context)); - - _this.close = _this.close.bind(_this); - _this.state = { open: false }; - return _this; - } - - _createClass(ViewSelector, [{ - key: 'close', - value: function close() { - this.setState({ open: false }); - document.removeEventListener('click', this.close); - } - }, { - key: 'onDropdown', - value: function onDropdown(e) { - e.preventDefault(); - this.setState({ open: !this.state.open }); - document.addEventListener('click', this.close); - } - }, { - key: 'render', - value: function render() { - var _this2 = this; - - var _props = this.props; - var contentViews = _props.contentViews; - var activeView = _props.activeView; - var isEdit = _props.isEdit; - var setContentView = _props.setContentView; - - var edit = ContentViews.Edit.displayName; - - return _react2.default.createElement( - 'div', - { className: (0, _classnames2.default)('dropup pull-left', { open: this.state.open }) }, - _react2.default.createElement( - 'a', - { className: 'btn btn-default btn-xs', - onClick: function onClick(e) { - return _this2.onDropdown(e); - }, - href: '#' }, - _react2.default.createElement( - 'b', - null, - 'View:' - ), - ' ', - activeView, - _react2.default.createElement('span', { className: 'caret' }) - ), - _react2.default.createElement( - 'ul', - { className: 'dropdown-menu', role: 'menu' }, - contentViews.map(function (name) { - return _react2.default.createElement( - ViewItem, - { key: name, setContentView: setContentView, name: name }, - name.toLowerCase().replace('_', ' ') - ); - }), - isEdit && _react2.default.createElement( - ViewItem, - { key: edit, setContentView: setContentView, name: edit }, - edit.toLowerCase() - ) - ) - ); - } - }]); - - return ViewSelector; -}(_react.Component); - exports.default = (0, _reactRedux.connect)(function (state) { return { contentViews: state.settings.contentViews, @@ -1083,7 +1017,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { setContentView: _flow.setContentView })(ViewSelector); -},{"../../ducks/ui/flow":54,"./ContentViews":8,"classnames":"classnames","react":"react","react-redux":"react-redux"}],14:[function(require,module,exports){ +},{"../../ducks/ui/flow":56,"../common/Dropdown":41,"./ContentViews":8,"react":"react","react-redux":"react-redux"}],14:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1210,7 +1144,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { toggleFilter: _eventLog.toggleFilter })(EventLog); -},{"../ducks/eventLog":48,"./EventLog/EventList":15,"./common/ToggleButton":42,"react":"react","react-redux":"react-redux"}],15:[function(require,module,exports){ +},{"../ducks/eventLog":50,"./EventLog/EventList":15,"./common/ToggleButton":44,"react":"react","react-redux":"react-redux"}],15:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1357,7 +1291,7 @@ function LogIcon(_ref) { exports.default = (0, _AutoScroll2.default)(EventLogList); -},{"../helpers/AutoScroll":44,"../helpers/VirtualScroll":45,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],16:[function(require,module,exports){ +},{"../helpers/AutoScroll":46,"../helpers/VirtualScroll":47,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],16:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1548,7 +1482,7 @@ FlowTable.defaultProps = { }; exports.default = (0, _AutoScroll2.default)(FlowTable); -},{"../filt/filt":61,"./FlowTable/FlowRow":18,"./FlowTable/FlowTableHead":19,"./helpers/AutoScroll":44,"./helpers/VirtualScroll":45,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],17:[function(require,module,exports){ +},{"../filt/filt":63,"./FlowTable/FlowRow":18,"./FlowTable/FlowTableHead":19,"./helpers/AutoScroll":46,"./helpers/VirtualScroll":47,"react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],17:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1705,7 +1639,7 @@ TimeColumn.headerName = 'Time'; exports.default = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, SizeColumn, TimeColumn]; -},{"../../flow/utils.js":62,"../../utils.js":63,"classnames":"classnames","react":"react"}],18:[function(require,module,exports){ +},{"../../flow/utils.js":64,"../../utils.js":65,"classnames":"classnames","react":"react"}],18:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1762,7 +1696,7 @@ function FlowRow(_ref) { exports.default = (0, _utils.pure)(FlowRow); -},{"../../utils":63,"./FlowColumns":17,"classnames":"classnames","react":"react"}],19:[function(require,module,exports){ +},{"../../utils":65,"./FlowColumns":17,"classnames":"classnames","react":"react"}],19:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1826,7 +1760,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { updateSort: _flowView.updateSort })(FlowTableHead); -},{"../../ducks/flowView":49,"./FlowColumns":17,"classnames":"classnames","react":"react","react-redux":"react-redux"}],20:[function(require,module,exports){ +},{"../../ducks/flowView":51,"./FlowColumns":17,"classnames":"classnames","react":"react","react-redux":"react-redux"}],20:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -1966,7 +1900,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { selectTab: _flow.selectTab })(FlowView); -},{"../ducks/ui/flow":54,"./FlowView/Details":21,"./FlowView/Messages":23,"./FlowView/Nav":24,"./Prompt":36,"lodash":"lodash","react":"react","react-redux":"react-redux"}],21:[function(require,module,exports){ +},{"../ducks/ui/flow":56,"./FlowView/Details":21,"./FlowView/Messages":23,"./FlowView/Nav":24,"./Prompt":36,"lodash":"lodash","react":"react","react-redux":"react-redux"}],21:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -2171,7 +2105,7 @@ function Details(_ref5) { return _react2.default.createElement( 'section', - null, + { className: 'detail' }, _react2.default.createElement( 'h4', null, @@ -2189,7 +2123,7 @@ function Details(_ref5) { ); } -},{"../../utils.js":63,"lodash":"lodash","react":"react"}],22:[function(require,module,exports){ +},{"../../utils.js":65,"lodash":"lodash","react":"react"}],22:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -2430,7 +2364,7 @@ Headers.propTypes = { }; exports.default = Headers; -},{"../../utils":63,"../ValueEditor/ValueEditor":39,"react":"react","react-dom":"react-dom"}],23:[function(require,module,exports){ +},{"../../utils":65,"../ValueEditor/ValueEditor":39,"react":"react","react-dom":"react-dom"}],23:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -2774,7 +2708,7 @@ function ErrorView(_ref3) { return _react2.default.createElement( 'section', - null, + { className: 'error' }, _react2.default.createElement( 'div', { className: 'alert alert-warning' }, @@ -2792,7 +2726,7 @@ function ErrorView(_ref3) { ); } -},{"../../ducks/flows":50,"../../ducks/ui/flow":54,"../../flow/utils.js":62,"../../utils.js":63,"../ContentView":4,"../ContentView/ContentViewOptions":7,"../ValueEditor/ValidateEditor":38,"../ValueEditor/ValueEditor":39,"./Headers":22,"./ToggleEdit":25,"react":"react","react-redux":"react-redux"}],24:[function(require,module,exports){ +},{"../../ducks/flows":52,"../../ducks/ui/flow":56,"../../flow/utils.js":64,"../../utils.js":65,"../ContentView":4,"../ContentView/ContentViewOptions":7,"../ValueEditor/ValidateEditor":38,"../ValueEditor/ValueEditor":39,"./Headers":22,"./ToggleEdit":25,"react":"react","react-redux":"react-redux"}],24:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -2927,7 +2861,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { stopEdit: _flow.stopEdit })(ToggleEdit); -},{"../../ducks/ui/flow":54,"react":"react","react-redux":"react-redux"}],26:[function(require,module,exports){ +},{"../../ducks/ui/flow":56,"react":"react","react-redux":"react-redux"}],26:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -2950,69 +2884,80 @@ Footer.propTypes = { function Footer(_ref) { var settings = _ref.settings; + var mode = settings.mode; + var intercept = settings.intercept; + var showhost = settings.showhost; + var no_upstream_cert = settings.no_upstream_cert; + var rawtcp = settings.rawtcp; + var http2 = settings.http2; + var anticache = settings.anticache; + var anticomp = settings.anticomp; + var stickyauth = settings.stickyauth; + var stickycookie = settings.stickycookie; + var stream = settings.stream; return _react2.default.createElement( 'footer', null, - settings.mode && settings.mode != "regular" && _react2.default.createElement( + mode && mode != "regular" && _react2.default.createElement( 'span', { className: 'label label-success' }, - settings.mode, + mode, ' mode' ), - settings.intercept && _react2.default.createElement( + intercept && _react2.default.createElement( 'span', { className: 'label label-success' }, 'Intercept: ', - settings.intercept + intercept ), - settings.showhost && _react2.default.createElement( + showhost && _react2.default.createElement( 'span', { className: 'label label-success' }, 'showhost' ), - settings.no_upstream_cert && _react2.default.createElement( + no_upstream_cert && _react2.default.createElement( 'span', { className: 'label label-success' }, 'no-upstream-cert' ), - settings.rawtcp && _react2.default.createElement( + rawtcp && _react2.default.createElement( 'span', { className: 'label label-success' }, 'raw-tcp' ), - !settings.http2 && _react2.default.createElement( + !http2 && _react2.default.createElement( 'span', { className: 'label label-success' }, 'no-http2' ), - settings.anticache && _react2.default.createElement( + anticache && _react2.default.createElement( 'span', { className: 'label label-success' }, 'anticache' ), - settings.anticomp && _react2.default.createElement( + anticomp && _react2.default.createElement( 'span', { className: 'label label-success' }, 'anticomp' ), - settings.stickyauth && _react2.default.createElement( + stickyauth && _react2.default.createElement( 'span', { className: 'label label-success' }, 'stickyauth: ', - settings.stickyauth + stickyauth ), - settings.stickycookie && _react2.default.createElement( + stickycookie && _react2.default.createElement( 'span', { className: 'label label-success' }, 'stickycookie: ', - settings.stickycookie + stickycookie ), - settings.stream && _react2.default.createElement( + stream && _react2.default.createElement( 'span', { className: 'label label-success' }, 'stream: ', - (0, _utils.formatSize)(settings.stream) + (0, _utils.formatSize)(stream) ) ); } @@ -3023,7 +2968,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { }; })(Footer); -},{"../utils.js":63,"react":"react","react-redux":"react-redux"}],27:[function(require,module,exports){ +},{"../utils.js":65,"react":"react","react-redux":"react-redux"}],27:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3148,24 +3093,26 @@ exports.default = (0, _reactRedux.connect)(function (state) { setActiveMenu: _header.setActiveMenu })(Header); -},{"../ducks/ui/header":55,"./Header/FileMenu":28,"./Header/FlowMenu":31,"./Header/MainMenu":32,"./Header/OptionMenu":33,"./Header/ViewMenu":34,"classnames":"classnames","react":"react","react-redux":"react-redux"}],28:[function(require,module,exports){ +},{"../ducks/ui/header":57,"./Header/FileMenu":28,"./Header/FlowMenu":31,"./Header/MainMenu":32,"./Header/OptionMenu":33,"./Header/ViewMenu":34,"classnames":"classnames","react":"react","react-redux":"react-redux"}],28:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactRedux = require('react-redux'); -var _classnames = require('classnames'); +var _FileChooser = require('../common/FileChooser'); -var _classnames2 = _interopRequireDefault(_classnames); +var _FileChooser2 = _interopRequireDefault(_FileChooser); + +var _Dropdown = require('../common/Dropdown'); + +var _Dropdown2 = _interopRequireDefault(_Dropdown); var _flows = require('../../ducks/flows'); @@ -3175,150 +3122,57 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -var FileMenu = function (_Component) { - _inherits(FileMenu, _Component); - - function FileMenu(props, context) { - _classCallCheck(this, FileMenu); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(FileMenu).call(this, props, context)); - - _this.state = { show: false }; - - _this.close = _this.close.bind(_this); - _this.onFileClick = _this.onFileClick.bind(_this); - _this.onNewClick = _this.onNewClick.bind(_this); - _this.onOpenClick = _this.onOpenClick.bind(_this); - _this.onOpenFile = _this.onOpenFile.bind(_this); - _this.onSaveClick = _this.onSaveClick.bind(_this); - return _this; - } +FileMenu.propTypes = { + clearFlows: _react.PropTypes.func.isRequired, + loadFlows: _react.PropTypes.func.isRequired, + saveFlows: _react.PropTypes.func.isRequired +}; - _createClass(FileMenu, [{ - key: 'close', - value: function close() { - this.setState({ show: false }); - document.removeEventListener('click', this.close); - } - }, { - key: 'onFileClick', - value: function onFileClick(e) { - e.preventDefault(); +FileMenu.onNewClick = function (e, clearFlows) { + e.preventDefault(); + if (confirm('Delete all flows?')) clearFlows(); +}; - if (this.state.show) { - return; - } +function FileMenu(_ref) { + var clearFlows = _ref.clearFlows; + var loadFlows = _ref.loadFlows; + var saveFlows = _ref.saveFlows; - document.addEventListener('click', this.close); - this.setState({ show: true }); - } - }, { - key: 'onNewClick', - value: function onNewClick(e) { - e.preventDefault(); - if (confirm('Delete all flows?')) { - this.props.clearFlows(); - } - } - }, { - key: 'onOpenClick', - value: function onOpenClick(e) { - e.preventDefault(); - this.fileInput.click(); - } - }, { - key: 'onOpenFile', - value: function onOpenFile(e) { - e.preventDefault(); - if (e.target.files.length > 0) { - this.props.loadFlows(e.target.files[0]); - this.fileInput.value = ''; + return _react2.default.createElement( + _Dropdown2.default, + { className: 'pull-left', btnClass: 'special', text: 'mitmproxy' }, + _react2.default.createElement( + 'a', + { href: '#', onClick: function onClick(e) { + return FileMenu.onNewClick(e, clearFlows); + } }, + _react2.default.createElement('i', { className: 'fa fa-fw fa-file' }), + 'New' + ), + _react2.default.createElement(_FileChooser2.default, { + icon: 'fa-folder-open', + text: 'Open...', + onOpenFile: function onOpenFile(file) { + return loadFlows(file); } - } - }, { - key: 'onSaveClick', - value: function onSaveClick(e) { - e.preventDefault(); - this.props.saveFlows(); - } - }, { - key: 'render', - value: function render() { - var _this2 = this; - - return _react2.default.createElement( - 'div', - { className: (0, _classnames2.default)('dropdown pull-left', { open: this.state.show }) }, - _react2.default.createElement( - 'a', - { href: '#', className: 'special', onClick: this.onFileClick }, - 'mitmproxy' - ), - _react2.default.createElement( - 'ul', - { className: 'dropdown-menu', role: 'menu' }, - _react2.default.createElement( - 'li', - null, - _react2.default.createElement( - 'a', - { href: '#', onClick: this.onNewClick }, - _react2.default.createElement('i', { className: 'fa fa-fw fa-file' }), - 'New' - ) - ), - _react2.default.createElement( - 'li', - null, - _react2.default.createElement( - 'a', - { href: '#', onClick: this.onOpenClick }, - _react2.default.createElement('i', { className: 'fa fa-fw fa-folder-open' }), - 'Open...' - ), - _react2.default.createElement('input', { - ref: function ref(_ref) { - return _this2.fileInput = _ref; - }, - className: 'hidden', - type: 'file', - onChange: this.onOpenFile - }) - ), - _react2.default.createElement( - 'li', - null, - _react2.default.createElement( - 'a', - { href: '#', onClick: this.onSaveClick }, - _react2.default.createElement('i', { className: 'fa fa-fw fa-floppy-o' }), - 'Save...' - ) - ), - _react2.default.createElement('li', { role: 'presentation', className: 'divider' }), - _react2.default.createElement( - 'li', - null, - _react2.default.createElement( - 'a', - { href: 'http://mitm.it/', target: '_blank' }, - _react2.default.createElement('i', { className: 'fa fa-fw fa-external-link' }), - 'Install Certificates...' - ) - ) - ) - ); - } - }]); - - return FileMenu; -}(_react.Component); + }), + _react2.default.createElement( + 'a', + { href: '#', onClick: function onClick(e) { + e.preventDefault();saveFlows(); + } }, + _react2.default.createElement('i', { className: 'fa fa-fw fa-floppy-o' }), + 'Save...' + ), + _react2.default.createElement(_Dropdown.Divider, null), + _react2.default.createElement( + 'a', + { href: 'http://mitm.it/', target: '_blank' }, + _react2.default.createElement('i', { className: 'fa fa-fw fa-external-link' }), + 'Install Certificates...' + ) + ); +} exports.default = (0, _reactRedux.connect)(null, { clearFlows: flowsActions.clear, @@ -3326,7 +3180,7 @@ exports.default = (0, _reactRedux.connect)(null, { saveFlows: flowsActions.download })(FileMenu); -},{"../../ducks/flows":50,"classnames":"classnames","react":"react","react-redux":"react-redux"}],29:[function(require,module,exports){ +},{"../../ducks/flows":52,"../common/Dropdown":41,"../common/FileChooser":42,"react":"react","react-redux":"react-redux"}],29:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3437,7 +3291,7 @@ FilterDocs.xhr = null; FilterDocs.doc = null; exports.default = FilterDocs; -},{"../../utils":63,"react":"react"}],30:[function(require,module,exports){ +},{"../../utils":65,"react":"react"}],30:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3631,7 +3485,7 @@ var FilterInput = function (_Component) { exports.default = FilterInput; -},{"../../filt/filt":61,"../../utils.js":63,"./FilterDocs":29,"classnames":"classnames","react":"react","react-dom":"react-dom"}],31:[function(require,module,exports){ +},{"../../filt/filt":63,"../../utils.js":65,"./FilterDocs":29,"classnames":"classnames","react":"react","react-dom":"react-dom"}],31:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3661,7 +3515,12 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de FlowMenu.title = 'Flow'; FlowMenu.propTypes = { - flow: _react.PropTypes.object.isRequired + flow: _react.PropTypes.object.isRequired, + acceptFlow: _react.PropTypes.func.isRequired, + replayFlow: _react.PropTypes.func.isRequired, + duplicateFlow: _react.PropTypes.func.isRequired, + removeFlow: _react.PropTypes.func.isRequired, + revertFlow: _react.PropTypes.func.isRequired }; function FlowMenu(_ref) { @@ -3672,7 +3531,6 @@ function FlowMenu(_ref) { var removeFlow = _ref.removeFlow; var revertFlow = _ref.revertFlow; - return _react2.default.createElement( 'div', null, @@ -3714,7 +3572,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { revertFlow: flowsActions.revert })(FlowMenu); -},{"../../ducks/flows":50,"../../flow/utils.js":62,"../common/Button":40,"react":"react","react-redux":"react-redux"}],32:[function(require,module,exports){ +},{"../../ducks/flows":52,"../../flow/utils.js":64,"../common/Button":40,"react":"react","react-redux":"react-redux"}],32:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3784,7 +3642,7 @@ var HighlightInput = (0, _reactRedux.connect)(function (state) { }; }, { onChange: _flowView.updateHighlight })(_FilterInput2.default); -},{"../../ducks/flowView":49,"../../ducks/settings":53,"./FilterInput":30,"react":"react","react-redux":"react-redux"}],33:[function(require,module,exports){ +},{"../../ducks/flowView":51,"../../ducks/settings":55,"./FilterInput":30,"react":"react","react-redux":"react-redux"}],33:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3864,21 +3722,21 @@ function OptionMenu(_ref) { }), _react2.default.createElement(_ToggleInputButton2.default, { name: 'stickyauth', placeholder: 'Sticky auth filter', checked: !!settings.stickyauth, - txt: settings.stickyauth || '', + txt: settings.stickyauth, onToggleChanged: function onToggleChanged(txt) { return updateSettings({ stickyauth: !settings.stickyauth ? txt : null }); } }), _react2.default.createElement(_ToggleInputButton2.default, { name: 'stickycookie', placeholder: 'Sticky cookie filter', checked: !!settings.stickycookie, - txt: settings.stickycookie || '', + txt: settings.stickycookie, onToggleChanged: function onToggleChanged(txt) { return updateSettings({ stickycookie: !settings.stickycookie ? txt : null }); } }), _react2.default.createElement(_ToggleInputButton2.default, { name: 'stream', placeholder: 'stream...', checked: !!settings.stream, - txt: settings.stream || '', + txt: settings.stream, inputType: 'number', onToggleChanged: function onToggleChanged(txt) { return updateSettings({ stream: !settings.stream ? txt : null }); @@ -3897,7 +3755,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { updateSettings: _settings.update })(OptionMenu); -},{"../../ducks/settings":53,"../common/ToggleButton":42,"../common/ToggleInputButton":43,"react":"react","react-redux":"react-redux"}],34:[function(require,module,exports){ +},{"../../ducks/settings":55,"../common/ToggleButton":44,"../common/ToggleInputButton":45,"react":"react","react-redux":"react-redux"}],34:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -3950,7 +3808,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { toggleEventLog: _eventLog.toggleVisibility })(ViewMenu); -},{"../../ducks/eventLog":48,"../common/ToggleButton":42,"react":"react","react-redux":"react-redux"}],35:[function(require,module,exports){ +},{"../../ducks/eventLog":50,"../common/ToggleButton":44,"react":"react","react-redux":"react-redux"}],35:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4025,8 +3883,7 @@ var MainView = function (_Component) { selectedFlow && [_react2.default.createElement(_Splitter2.default, { key: 'splitter' }), _react2.default.createElement(_FlowView2.default, { key: 'flowDetails', ref: 'flowDetails', - tab: this.props.routeParams.detailTab, - query: this.props.query, + tab: this.props.tab, updateFlow: function updateFlow(data) { return _this2.props.updateFlow(selectedFlow, data); }, @@ -4048,7 +3905,8 @@ exports.default = (0, _reactRedux.connect)(function (state) { flows: state.flowView.data, filter: state.flowView.filter, highlight: state.flowView.highlight, - selectedFlow: state.flows.byId[state.flows.selected[0]] + selectedFlow: state.flows.byId[state.flows.selected[0]], + tab: state.ui.flow.tab }; }, { selectFlow: flowsActions.select, @@ -4057,7 +3915,7 @@ exports.default = (0, _reactRedux.connect)(function (state) { updateFlow: flowsActions.update })(MainView); -},{"../ducks/flowView":49,"../ducks/flows":50,"./FlowTable":16,"./FlowView":20,"./common/Splitter":41,"react":"react","react-redux":"react-redux"}],36:[function(require,module,exports){ +},{"../ducks/flowView":51,"../ducks/flows":52,"./FlowTable":16,"./FlowView":20,"./common/Splitter":43,"react":"react","react-redux":"react-redux"}],36:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4158,13 +4016,15 @@ function Prompt(_ref) { ); } -},{"../utils.js":63,"lodash":"lodash","react":"react","react-dom":"react-dom"}],37:[function(require,module,exports){ +},{"../utils.js":65,"lodash":"lodash","react":"react","react-dom":"react-dom"}],37:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); @@ -4173,10 +4033,24 @@ var _react2 = _interopRequireDefault(_react); var _reactRedux = require('react-redux'); +var _history = require('history'); + var _app = require('../ducks/app'); var _keyboard = require('../ducks/ui/keyboard'); +var _flowView = require('../ducks/flowView'); + +var _flow = require('../ducks/ui/flow'); + +var _flows = require('../ducks/flows'); + +var _actions = require('../actions'); + +var _MainView = require('./MainView'); + +var _MainView2 = _interopRequireDefault(_MainView); + var _Header = require('./Header'); var _Header2 = _interopRequireDefault(_Header); @@ -4207,37 +4081,66 @@ var ProxyAppMain = function (_Component) { } _createClass(ProxyAppMain, [{ + key: 'flushToStore', + value: function flushToStore(location) { + var components = location.pathname.split('/').filter(function (v) { + return v; + }); + var query = location.query || {}; + + if (components.length > 2) { + this.props.selectFlow(components[1]); + this.props.selectTab(components[2]); + } else { + this.props.selectFlow(null); + this.props.selectTab(null); + } + + this.props.updateFilter(query[_actions.Query.SEARCH]); + this.props.updateHighlight(query[_actions.Query.HIGHLIGHT]); + } + }, { + key: 'flushToHistory', + value: function flushToHistory(props) { + var query = _extends({}, query); + + if (props.filter) { + query[_actions.Query.SEARCH] = props.filter; + } + + if (props.highlight) { + query[_actions.Query.HIGHLIGHT] = props.highlight; + } + + if (props.selectedFlowId) { + this.history.push({ pathname: '/flows/' + props.selectedFlowId + '/' + props.tab, query: query }); + } else { + this.history.push({ pathname: '/flows', query: query }); + } + } + }, { key: 'componentWillMount', value: function componentWillMount() { - this.props.appInit(this.context.router); + var _this2 = this; + + this.props.appInit(); + this.history = (0, _history.useQueries)(_history.createHashHistory)(); + this.unlisten = this.history.listen(function (location) { + return _this2.flushToStore(location); + }); window.addEventListener('keydown', this.props.onKeyDown); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { - this.props.appDestruct(this.context.router); + this.props.appDestruct(); + this.unlisten(); window.removeEventListener('keydown', this.props.onKeyDown); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { - /* - FIXME: improve react-router -> redux integration. - if (nextProps.location.query[Query.SEARCH] !== nextProps.filter) { - this.props.updateFilter(nextProps.location.query[Query.SEARCH], false) - } - if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { - this.props.updateHighlight(nextProps.location.query[Query.HIGHLIGHT], false) - } - */ - if (nextProps.query === this.props.query && nextProps.selectedFlowId === this.props.selectedFlowId && nextProps.panel === this.props.panel) { - return; - } - if (nextProps.selectedFlowId) { - this.context.router.replace({ pathname: '/flows/' + nextProps.selectedFlowId + '/' + nextProps.panel, query: nextProps.query }); - } else { - this.context.router.replace({ pathname: '/flows', query: nextProps.query }); - } + this.flushToHistory(nextProps); } }, { key: 'render', @@ -4245,14 +4148,14 @@ var ProxyAppMain = function (_Component) { var _props = this.props; var showEventLog = _props.showEventLog; var location = _props.location; - var children = _props.children; - var query = _props.query; + var filter = _props.filter; + var highlight = _props.highlight; return _react2.default.createElement( 'div', { id: 'container', tabIndex: '0' }, _react2.default.createElement(_Header2.default, null), - _react2.default.cloneElement(children, { ref: 'view', location: location, query: query }), + _react2.default.createElement(_MainView2.default, null), showEventLog && _react2.default.createElement(_EventLog2.default, { key: 'eventlog' }), _react2.default.createElement(_Footer2.default, null) ); @@ -4262,23 +4165,25 @@ var ProxyAppMain = function (_Component) { return ProxyAppMain; }(_react.Component); -ProxyAppMain.contextTypes = { - router: _react.PropTypes.object.isRequired -}; exports.default = (0, _reactRedux.connect)(function (state) { return { showEventLog: state.eventLog.visible, - query: state.flowView.filter, - panel: state.ui.flow.tab, + filter: state.flowView.filter, + highlight: state.flowView.highlight, + tab: state.ui.flow.tab, selectedFlowId: state.flows.selected[0] }; }, { appInit: _app.init, appDestruct: _app.destruct, - onKeyDown: _keyboard.onKeyDown + onKeyDown: _keyboard.onKeyDown, + updateFilter: _flowView.updateFilter, + updateHighlight: _flowView.updateHighlight, + selectTab: _flow.selectTab, + selectFlow: _flows.select })(ProxyAppMain); -},{"../ducks/app":47,"../ducks/ui/keyboard":57,"./EventLog":14,"./Footer":26,"./Header":27,"react":"react","react-redux":"react-redux"}],38:[function(require,module,exports){ +},{"../actions":2,"../ducks/app":49,"../ducks/flowView":51,"../ducks/flows":52,"../ducks/ui/flow":56,"../ducks/ui/keyboard":59,"./EventLog":14,"./Footer":26,"./Header":27,"./MainView":35,"history":"history","react":"react","react-redux":"react-redux"}],38:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4587,7 +4492,7 @@ ValueEditor.defaultProps = { }; exports.default = ValueEditor; -},{"../../utils":63,"classnames":"classnames","lodash":"lodash","react":"react"}],40:[function(require,module,exports){ +},{"../../utils":65,"classnames":"classnames","lodash":"lodash","react":"react"}],40:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4637,6 +4542,168 @@ Object.defineProperty(exports, "__esModule", { var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); +exports.Divider = Divider; + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function Divider() { + return _react2.default.createElement('hr', { className: 'divider' }); +} + +var Dropdown = function (_Component) { + _inherits(Dropdown, _Component); + + function Dropdown(props, context) { + _classCallCheck(this, Dropdown); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Dropdown).call(this, props, context)); + + _this.state = { open: false }; + _this.close = _this.close.bind(_this); + _this.open = _this.open.bind(_this); + return _this; + } + + _createClass(Dropdown, [{ + key: 'close', + value: function close() { + this.setState({ open: false }); + document.removeEventListener('click', this.close); + } + }, { + key: 'open', + value: function open(e) { + e.preventDefault(); + if (this.state.open) { + return; + } + this.setState({ open: !this.state.open }); + document.addEventListener('click', this.close); + } + }, { + key: 'render', + value: function render() { + var _props = this.props; + var dropup = _props.dropup; + var className = _props.className; + var btnClass = _props.btnClass; + var text = _props.text; + var children = _props.children; + + return _react2.default.createElement( + 'div', + { className: (0, _classnames2.default)(dropup ? 'dropup' : 'dropdown', className, { open: this.state.open }) }, + _react2.default.createElement( + 'a', + { href: '#', className: btnClass, + onClick: this.open }, + text + ), + _react2.default.createElement( + 'ul', + { className: 'dropdown-menu', role: 'menu' }, + children.map(function (item, i) { + return _react2.default.createElement( + 'li', + { key: i }, + ' ', + item, + ' ' + ); + }) + ) + ); + } + }]); + + return Dropdown; +}(_react.Component); + +Dropdown.propTypes = { + dropup: _react.PropTypes.bool, + className: _react.PropTypes.string, + btnClass: _react.PropTypes.string.isRequired +}; +Dropdown.defaultProps = { + dropup: false +}; +exports.default = Dropdown; + +},{"classnames":"classnames","react":"react"}],42:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = FileChooser; + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +FileChooser.propTypes = { + icon: _react.PropTypes.string, + text: _react.PropTypes.string, + className: _react.PropTypes.string, + title: _react.PropTypes.string, + onOpenFile: _react.PropTypes.func.isRequired +}; + +function FileChooser(_ref) { + var icon = _ref.icon; + var text = _ref.text; + var className = _ref.className; + var title = _ref.title; + var onOpenFile = _ref.onOpenFile; + + var fileInput = void 0; + return _react2.default.createElement( + 'a', + { href: '#', onClick: function onClick() { + return fileInput.click(); + }, + className: className, + title: title }, + _react2.default.createElement('i', { className: 'fa fa-fw ' + icon }), + text, + _react2.default.createElement('input', { + ref: function ref(_ref2) { + return fileInput = _ref2; + }, + className: 'hidden', + type: 'file', + onChange: function onChange(e) { + e.preventDefault();if (e.target.files.length > 0) onOpenFile(e.target.files[0]);fileInput = ""; + } + }) + ); +} + +},{"react":"react"}],43:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + var _react = require('react'); var _react2 = _interopRequireDefault(_react); @@ -4773,7 +4840,7 @@ var Splitter = function (_Component) { Splitter.defaultProps = { axis: 'x' }; exports.default = Splitter; -},{"classnames":"classnames","react":"react","react-dom":"react-dom"}],42:[function(require,module,exports){ +},{"classnames":"classnames","react":"react","react-dom":"react-dom"}],44:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4807,7 +4874,7 @@ function ToggleButton(_ref) { ); } -},{"react":"react"}],43:[function(require,module,exports){ +},{"react":"react"}],45:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4842,16 +4909,11 @@ var ToggleInputButton = function (_Component) { var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ToggleInputButton).call(this, props)); - _this.state = { txt: props.txt }; + _this.state = { txt: props.txt || '' }; return _this; } _createClass(ToggleInputButton, [{ - key: 'onChange', - value: function onChange(e) { - this.setState({ txt: e.target.value }); - } - }, { key: 'onKeyDown', value: function onKeyDown(e) { e.stopPropagation(); @@ -4864,6 +4926,13 @@ var ToggleInputButton = function (_Component) { value: function render() { var _this2 = this; + var _props = this.props; + var checked = _props.checked; + var onToggleChanged = _props.onToggleChanged; + var name = _props.name; + var inputType = _props.inputType; + var placeholder = _props.placeholder; + return _react2.default.createElement( 'div', { className: 'input-group toggle-input-btn' }, @@ -4871,24 +4940,24 @@ var ToggleInputButton = function (_Component) { 'span', { className: 'input-group-btn', onClick: function onClick() { - return _this2.props.onToggleChanged(_this2.state.txt); + return onToggleChanged(_this2.state.txt); } }, _react2.default.createElement( 'div', - { className: (0, _classnames2.default)('btn', this.props.checked ? 'btn-primary' : 'btn-default') }, - _react2.default.createElement('span', { className: (0, _classnames2.default)('fa', this.props.checked ? 'fa-check-square-o' : 'fa-square-o') }), + { className: (0, _classnames2.default)('btn', checked ? 'btn-primary' : 'btn-default') }, + _react2.default.createElement('span', { className: (0, _classnames2.default)('fa', checked ? 'fa-check-square-o' : 'fa-square-o') }), ' ', - this.props.name + name ) ), _react2.default.createElement('input', { className: 'form-control', - placeholder: this.props.placeholder, - disabled: this.props.checked, + placeholder: placeholder, + disabled: checked, value: this.state.txt, - type: this.props.inputType, + type: inputType || 'text', onChange: function onChange(e) { - return _this2.onChange(e); + return _this2.setState({ txt: e.target.value }); }, onKeyDown: function onKeyDown(e) { return _this2.onKeyDown(e); @@ -4903,12 +4972,15 @@ var ToggleInputButton = function (_Component) { ToggleInputButton.propTypes = { name: _react.PropTypes.string.isRequired, - txt: _react.PropTypes.string.isRequired, - onToggleChanged: _react.PropTypes.func.isRequired + txt: _react.PropTypes.string, + onToggleChanged: _react.PropTypes.func.isRequired, + checked: _react.PropTypes.bool.isRequired, + placeholder: _react.PropTypes.string.isRequired, + inputType: _react.PropTypes.string }; exports.default = ToggleInputButton; -},{"../../utils":63,"classnames":"classnames","react":"react"}],44:[function(require,module,exports){ +},{"../../utils":65,"classnames":"classnames","react":"react"}],46:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4974,7 +5046,7 @@ exports.default = function (Component) { }(Component), _class.displayName = Component.name, _temp), Component); }; -},{"react":"react","react-dom":"react-dom"}],45:[function(require,module,exports){ +},{"react":"react","react-dom":"react-dom"}],47:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5053,7 +5125,7 @@ function calcVScroll(opts) { return { start: start, end: end, paddingTop: paddingTop, paddingBottom: paddingBottom }; } -},{}],46:[function(require,module,exports){ +},{}],48:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -5082,7 +5154,7 @@ AppDispatcher.dispatchServerAction = function (action) { this.dispatch(action); }; -},{"flux":"flux"}],47:[function(require,module,exports){ +},{"flux":"flux"}],49:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5124,7 +5196,7 @@ function destruct() { }; } -},{"./websocket":60}],48:[function(require,module,exports){ +},{"./websocket":62}],50:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5301,7 +5373,7 @@ function receiveData(list) { return { type: RECEIVE, list: list }; } -},{"./msgQueue":52,"./utils/list":58,"./utils/view":59,"./websocket":60}],49:[function(require,module,exports){ +},{"./msgQueue":54,"./utils/list":60,"./utils/view":61,"./websocket":62}],51:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5489,7 +5561,7 @@ function selectRelative(shift) { }; } -},{"../filt/filt":61,"../flow/utils":62,"./flows":50,"./utils/view":59}],50:[function(require,module,exports){ +},{"../filt/filt":63,"../flow/utils":64,"./flows":52,"./utils/view":61}],52:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5743,10 +5815,13 @@ function updateFlow(item) { * @private */ function removeFlow(id) { - return { type: REMOVE, id: id }; + return function (dispatch) { + dispatch(select()); + dispatch({ type: REMOVE, id: id }); + }; } -},{"../utils":63,"./msgQueue":52,"./utils/list":58,"./websocket":60}],51:[function(require,module,exports){ +},{"../utils":65,"./msgQueue":54,"./utils/list":60,"./websocket":62}],53:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5795,7 +5870,7 @@ exports.default = (0, _redux.combineReducers)({ msgQueue: _msgQueue2.default }); -},{"./eventLog":48,"./flowView":49,"./flows":50,"./msgQueue":52,"./settings":53,"./ui/index":56,"./websocket":60,"redux":"redux"}],52:[function(require,module,exports){ +},{"./eventLog":50,"./flowView":51,"./flows":52,"./msgQueue":54,"./settings":55,"./ui/index":58,"./websocket":62,"redux":"redux"}],54:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -5963,7 +6038,7 @@ function fetchError(type, error) { return _ref = { type: FETCH_ERROR }, _defineProperty(_ref, 'type', type), _defineProperty(_ref, 'error', error), _ref; } -},{"../utils":63,"./eventLog":48,"./flows":50,"./settings":53,"./websocket":60}],53:[function(require,module,exports){ +},{"../utils":65,"./eventLog":50,"./flows":52,"./settings":55,"./websocket":62}],55:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6063,13 +6138,13 @@ function updateSettings(settings) { return { type: UPDATE, settings: settings }; } -},{"../utils":63,"./msgQueue":52,"./websocket":60}],54:[function(require,module,exports){ +},{"../utils":65,"./msgQueue":54,"./websocket":62}],56:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.SET_CONTENT_VIEW_DESCRIPTION = exports.SET_SHOW_FULL_CONTENT = exports.UPLOAD_CONTENT = exports.UPDATE_EDIT = exports.START_EDIT = exports.SET_TAB = exports.DISPLAY_LARGE = exports.SET_CONTENT_VIEW = undefined; +exports.SET_CONTENT = exports.SET_CONTENT_VIEW_DESCRIPTION = exports.SET_SHOW_FULL_CONTENT = exports.UPLOAD_CONTENT = exports.UPDATE_EDIT = exports.START_EDIT = exports.SET_TAB = exports.DISPLAY_LARGE = exports.SET_CONTENT_VIEW = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; @@ -6082,6 +6157,7 @@ exports.updateEdit = updateEdit; exports.setContentViewDescription = setContentViewDescription; exports.setShowFullContent = setShowFullContent; exports.updateEdit = updateEdit; +exports.setContent = setContent; exports.stopEdit = stopEdit; var _flows = require('../flows'); @@ -6105,7 +6181,8 @@ var SET_CONTENT_VIEW = exports.SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW' UPDATE_EDIT = exports.UPDATE_EDIT = 'UI_FLOWVIEW_UPDATE_EDIT', UPLOAD_CONTENT = exports.UPLOAD_CONTENT = 'UI_FLOWVIEW_UPLOAD_CONTENT', SET_SHOW_FULL_CONTENT = exports.SET_SHOW_FULL_CONTENT = 'UI_SET_SHOW_FULL_CONTENT', - SET_CONTENT_VIEW_DESCRIPTION = exports.SET_CONTENT_VIEW_DESCRIPTION = "UI_SET_CONTENT_VIEW_DESCRIPTION"; + SET_CONTENT_VIEW_DESCRIPTION = exports.SET_CONTENT_VIEW_DESCRIPTION = "UI_SET_CONTENT_VIEW_DESCRIPTION", + SET_CONTENT = exports.SET_CONTENT = "UI_SET_CONTENT"; var defaultState = { displayLarge: false, @@ -6113,7 +6190,9 @@ var defaultState = { showFullContent: false, modifiedFlow: false, contentView: 'Auto', - tab: 'request' + tab: 'request', + content: [], + maxContentLines: 80 }; function reducer() { @@ -6172,7 +6251,7 @@ function reducer() { case SET_TAB: return _extends({}, state, { - tab: action.tab, + tab: action.tab ? action.tab : 'request', displayLarge: false, showFullContent: false }); @@ -6183,6 +6262,13 @@ function reducer() { showFullContent: action.contentView == 'Edit' }); + case SET_CONTENT: + var isFullContentShown = action.content.length < state.maxContentLines; + return _extends({}, state, { + content: action.content, + showFullContent: isFullContentShown + }); + case DISPLAY_LARGE: return _extends({}, state, { displayLarge: true @@ -6224,12 +6310,16 @@ function updateEdit(update) { return { type: UPDATE_EDIT, update: update }; } +function setContent(content) { + return { type: SET_CONTENT, content: content }; +} + function stopEdit(flow, modifiedFlow) { var diff = (0, _utils.getDiff)(flow, modifiedFlow); return flowsActions.update(flow, diff); } -},{"../../utils":63,"../flows":50,"lodash":"lodash"}],55:[function(require,module,exports){ +},{"../../utils":65,"../flows":52,"lodash":"lodash"}],57:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6296,7 +6386,7 @@ function setActiveMenu(activeMenu) { return { type: SET_ACTIVE_MENU, activeMenu: activeMenu }; } -},{"../flows":50}],56:[function(require,module,exports){ +},{"../flows":52}],58:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6320,7 +6410,7 @@ exports.default = (0, _redux.combineReducers)({ header: _header2.default }); -},{"./flow":54,"./header":55,"redux":"redux"}],57:[function(require,module,exports){ +},{"./flow":56,"./header":57,"redux":"redux"}],59:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6460,7 +6550,7 @@ function onKeyDown(e) { }; } -},{"../../utils":63,"../flowView":49,"../flows":50,"./flow":54}],58:[function(require,module,exports){ +},{"../../utils":65,"../flowView":51,"../flows":52,"./flow":56}],60:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6595,7 +6685,7 @@ function receive(list) { return { type: RECEIVE, list: list }; } -},{"lodash":"lodash"}],59:[function(require,module,exports){ +},{"lodash":"lodash"}],61:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6676,15 +6766,20 @@ function reduce() { return _extends({}, state, sortedRemove(state, action.id)); case UPDATE: - if (state.indexOf[action.item.id] == null) { - return; + var hasOldItem = state.indexOf[action.item.id] !== null && state.indexOf[action.item.id] !== undefined; + var hasNewItem = action.filter(action.item); + if (!hasNewItem && !hasOldItem) { + return state; } - var nextState = _extends({}, state, sortedRemove(state, action.item.id)); - if (!action.filter(action.item)) { - return nextState; + if (hasNewItem && !hasOldItem) { + return _extends({}, state, sortedInsert(state, action.item, action.sort)); + } + if (!hasNewItem && hasOldItem) { + return _extends({}, state, sortedRemove(state, action.item.id)); + } + if (hasNewItem && hasOldItem) { + return _extends({}, state, sortedUpdate(state, action.item, action.sort)); } - return _extends({}, nextState, sortedInsert(nextState, action.item, action.sort)); - case RECEIVE: { var _data2 = action.list.filter(action.filter).sort(action.sort); @@ -6765,6 +6860,28 @@ function sortedRemove(state, id) { return { data: data, indexOf: indexOf }; } +function sortedUpdate(state, item, sort) { + var data = [].concat(_toConsumableArray(state.data)); + var indexOf = _extends({}, state.indexOf); + var index = indexOf[item.id]; + data[index] = item; + while (index + 1 < data.length && sort(data[index], data[index + 1]) > 0) { + data[index] = data[index + 1]; + data[index + 1] = item; + indexOf[item.id] = index + 1; + indexOf[data[index].id] = index; + ++index; + } + while (index > 0 && sort(data[index], data[index - 1]) < 0) { + data[index] = data[index - 1]; + data[index - 1] = item; + indexOf[item.id] = index - 1; + indexOf[data[index].id] = index; + --index; + } + return { data: data, indexOf: indexOf }; +} + function sortedIndex(list, item, sort) { var low = 0; var high = list.length; @@ -6789,7 +6906,7 @@ function defaultSort(a, b) { return 0; } -},{"lodash":"lodash"}],60:[function(require,module,exports){ +},{"lodash":"lodash"}],62:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -6930,7 +7047,7 @@ function onError(error) { }; } -},{"../actions.js":2,"../dispatcher.js":46,"./eventLog":48,"./flows":50,"./msgQueue":52,"./settings":53}],61:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":48,"./eventLog":50,"./flows":52,"./msgQueue":54,"./settings":55}],63:[function(require,module,exports){ "use strict"; module.exports = function () { @@ -8834,7 +8951,7 @@ module.exports = function () { }; }(); -},{"../flow/utils.js":62}],62:[function(require,module,exports){ +},{"../flow/utils.js":64}],64:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -8953,7 +9070,7 @@ var isValidHttpVersion = exports.isValidHttpVersion = function isValidHttpVersio return isValidHttpVersion_regex.test(httpVersion); }; -},{"lodash":"lodash"}],63:[function(require,module,exports){ +},{"lodash":"lodash"}],65:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { diff --git a/mitmproxy/web/static/vendor.js b/mitmproxy/web/static/vendor.js index 7bdd0e3d..0d85e24c 100644 --- a/mitmproxy/web/static/vendor.js +++ b/mitmproxy/web/static/vendor.js @@ -9117,7 +9117,7 @@ var invariant = function (condition, format, a, b, c, d, e, f) { module.exports = invariant; }).call(this,require('_process')) -},{"_process":37}],6:[function(require,module,exports){ +},{"_process":40}],6:[function(require,module,exports){ (function (process){ /** * Copyright (c) 2014-2015, Facebook, Inc. @@ -9352,7 +9352,7 @@ var Dispatcher = (function () { module.exports = Dispatcher; }).call(this,require('_process')) -},{"_process":37,"fbjs/lib/invariant":5}],7:[function(require,module,exports){ +},{"_process":40,"fbjs/lib/invariant":5}],7:[function(require,module,exports){ /** * Indicates that navigation was caused by a call to history.push. */ @@ -9520,7 +9520,7 @@ function readState(key) { } }).call(this,require('_process')) -},{"_process":37,"warning":23}],10:[function(require,module,exports){ +},{"_process":40,"warning":26}],10:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -9653,7 +9653,7 @@ function parsePath(path) { } }).call(this,require('_process')) -},{"_process":37,"warning":23}],13:[function(require,module,exports){ +},{"_process":40,"warning":26}],13:[function(require,module,exports){ (function (process){ 'use strict'; @@ -9837,7 +9837,7 @@ exports['default'] = createBrowserHistory; module.exports = exports['default']; }).call(this,require('_process')) -},{"./Actions":7,"./DOMStateStorage":9,"./DOMUtils":10,"./ExecutionEnvironment":11,"./PathUtils":12,"./createDOMHistory":14,"_process":37,"invariant":25}],14:[function(require,module,exports){ +},{"./Actions":7,"./DOMStateStorage":9,"./DOMUtils":10,"./ExecutionEnvironment":11,"./PathUtils":12,"./createDOMHistory":14,"_process":40,"invariant":28}],14:[function(require,module,exports){ (function (process){ 'use strict'; @@ -9881,7 +9881,7 @@ exports['default'] = createDOMHistory; module.exports = exports['default']; }).call(this,require('_process')) -},{"./DOMUtils":10,"./ExecutionEnvironment":11,"./createHistory":16,"_process":37,"invariant":25}],15:[function(require,module,exports){ +},{"./DOMUtils":10,"./ExecutionEnvironment":11,"./createHistory":16,"_process":40,"invariant":28}],15:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10131,7 +10131,7 @@ exports['default'] = createHashHistory; module.exports = exports['default']; }).call(this,require('_process')) -},{"./Actions":7,"./DOMStateStorage":9,"./DOMUtils":10,"./ExecutionEnvironment":11,"./PathUtils":12,"./createDOMHistory":14,"_process":37,"invariant":25,"warning":23}],16:[function(require,module,exports){ +},{"./Actions":7,"./DOMStateStorage":9,"./DOMUtils":10,"./ExecutionEnvironment":11,"./PathUtils":12,"./createDOMHistory":14,"_process":40,"invariant":28,"warning":26}],16:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10423,7 +10423,7 @@ exports['default'] = createHistory; module.exports = exports['default']; }).call(this,require('_process')) -},{"./Actions":7,"./AsyncUtils":8,"./PathUtils":12,"./createLocation":17,"./deprecate":19,"./runTransitionHook":20,"_process":37,"deep-equal":2,"warning":23}],17:[function(require,module,exports){ +},{"./Actions":7,"./AsyncUtils":8,"./PathUtils":12,"./createLocation":17,"./deprecate":19,"./runTransitionHook":22,"_process":40,"deep-equal":2,"warning":26}],17:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10478,7 +10478,7 @@ exports['default'] = createLocation; module.exports = exports['default']; }).call(this,require('_process')) -},{"./Actions":7,"./PathUtils":12,"_process":37,"warning":23}],18:[function(require,module,exports){ +},{"./Actions":7,"./PathUtils":12,"_process":40,"warning":26}],18:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10636,7 +10636,7 @@ exports['default'] = createMemoryHistory; module.exports = exports['default']; }).call(this,require('_process')) -},{"./Actions":7,"./PathUtils":12,"./createHistory":16,"_process":37,"invariant":25,"warning":23}],19:[function(require,module,exports){ +},{"./Actions":7,"./PathUtils":12,"./createHistory":16,"_process":40,"invariant":28,"warning":26}],19:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10659,7 +10659,41 @@ exports['default'] = deprecate; module.exports = exports['default']; }).call(this,require('_process')) -},{"_process":37,"warning":23}],20:[function(require,module,exports){ +},{"_process":40,"warning":26}],20:[function(require,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _deprecate = require('./deprecate'); + +var _deprecate2 = _interopRequireDefault(_deprecate); + +var _useBeforeUnload = require('./useBeforeUnload'); + +var _useBeforeUnload2 = _interopRequireDefault(_useBeforeUnload); + +exports['default'] = _deprecate2['default'](_useBeforeUnload2['default'], 'enableBeforeUnload is deprecated, use useBeforeUnload instead'); +module.exports = exports['default']; +},{"./deprecate":19,"./useBeforeUnload":24}],21:[function(require,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _deprecate = require('./deprecate'); + +var _deprecate2 = _interopRequireDefault(_deprecate); + +var _useQueries = require('./useQueries'); + +var _useQueries2 = _interopRequireDefault(_useQueries); + +exports['default'] = _deprecate2['default'](_useQueries2['default'], 'enableQueries is deprecated, use useQueries instead'); +module.exports = exports['default']; +},{"./deprecate":19,"./useQueries":25}],22:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10687,7 +10721,7 @@ exports['default'] = runTransitionHook; module.exports = exports['default']; }).call(this,require('_process')) -},{"_process":37,"warning":23}],21:[function(require,module,exports){ +},{"_process":40,"warning":26}],23:[function(require,module,exports){ (function (process){ 'use strict'; @@ -10849,7 +10883,122 @@ exports['default'] = useBasename; module.exports = exports['default']; }).call(this,require('_process')) -},{"./ExecutionEnvironment":11,"./PathUtils":12,"./deprecate":19,"./runTransitionHook":20,"_process":37,"warning":23}],22:[function(require,module,exports){ +},{"./ExecutionEnvironment":11,"./PathUtils":12,"./deprecate":19,"./runTransitionHook":22,"_process":40,"warning":26}],24:[function(require,module,exports){ +(function (process){ +'use strict'; + +exports.__esModule = true; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _warning = require('warning'); + +var _warning2 = _interopRequireDefault(_warning); + +var _ExecutionEnvironment = require('./ExecutionEnvironment'); + +var _DOMUtils = require('./DOMUtils'); + +var _deprecate = require('./deprecate'); + +var _deprecate2 = _interopRequireDefault(_deprecate); + +function startBeforeUnloadListener(getBeforeUnloadPromptMessage) { + function listener(event) { + var message = getBeforeUnloadPromptMessage(); + + if (typeof message === 'string') { + (event || window.event).returnValue = message; + return message; + } + } + + _DOMUtils.addEventListener(window, 'beforeunload', listener); + + return function () { + _DOMUtils.removeEventListener(window, 'beforeunload', listener); + }; +} + +/** + * Returns a new createHistory function that can be used to create + * history objects that know how to use the beforeunload event in web + * browsers to cancel navigation. + */ +function useBeforeUnload(createHistory) { + return function (options) { + var history = createHistory(options); + + var stopBeforeUnloadListener = undefined; + var beforeUnloadHooks = []; + + function getBeforeUnloadPromptMessage() { + var message = undefined; + + for (var i = 0, len = beforeUnloadHooks.length; message == null && i < len; ++i) { + message = beforeUnloadHooks[i].call(); + }return message; + } + + function listenBeforeUnload(hook) { + beforeUnloadHooks.push(hook); + + if (beforeUnloadHooks.length === 1) { + if (_ExecutionEnvironment.canUseDOM) { + stopBeforeUnloadListener = startBeforeUnloadListener(getBeforeUnloadPromptMessage); + } else { + process.env.NODE_ENV !== 'production' ? _warning2['default'](false, 'listenBeforeUnload only works in DOM environments') : undefined; + } + } + + return function () { + beforeUnloadHooks = beforeUnloadHooks.filter(function (item) { + return item !== hook; + }); + + if (beforeUnloadHooks.length === 0 && stopBeforeUnloadListener) { + stopBeforeUnloadListener(); + stopBeforeUnloadListener = null; + } + }; + } + + // deprecated + function registerBeforeUnloadHook(hook) { + if (_ExecutionEnvironment.canUseDOM && beforeUnloadHooks.indexOf(hook) === -1) { + beforeUnloadHooks.push(hook); + + if (beforeUnloadHooks.length === 1) stopBeforeUnloadListener = startBeforeUnloadListener(getBeforeUnloadPromptMessage); + } + } + + // deprecated + function unregisterBeforeUnloadHook(hook) { + if (beforeUnloadHooks.length > 0) { + beforeUnloadHooks = beforeUnloadHooks.filter(function (item) { + return item !== hook; + }); + + if (beforeUnloadHooks.length === 0) stopBeforeUnloadListener(); + } + } + + return _extends({}, history, { + listenBeforeUnload: listenBeforeUnload, + + registerBeforeUnloadHook: _deprecate2['default'](registerBeforeUnloadHook, 'registerBeforeUnloadHook is deprecated; use listenBeforeUnload instead'), + unregisterBeforeUnloadHook: _deprecate2['default'](unregisterBeforeUnloadHook, 'unregisterBeforeUnloadHook is deprecated; use the callback returned from listenBeforeUnload instead') + }); + }; +} + +exports['default'] = useBeforeUnload; +module.exports = exports['default']; +}).call(this,require('_process')) + +},{"./DOMUtils":10,"./ExecutionEnvironment":11,"./deprecate":19,"_process":40,"warning":26}],25:[function(require,module,exports){ (function (process){ 'use strict'; @@ -11029,7 +11178,7 @@ exports['default'] = useQueries; module.exports = exports['default']; }).call(this,require('_process')) -},{"./PathUtils":12,"./deprecate":19,"./runTransitionHook":20,"_process":37,"query-string":38,"warning":23}],23:[function(require,module,exports){ +},{"./PathUtils":12,"./deprecate":19,"./runTransitionHook":22,"_process":40,"query-string":41,"warning":26}],26:[function(require,module,exports){ (function (process){ /** * Copyright 2014-2015, Facebook, Inc. @@ -11094,7 +11243,7 @@ module.exports = warning; }).call(this,require('_process')) -},{"_process":37}],24:[function(require,module,exports){ +},{"_process":40}],27:[function(require,module,exports){ /** * Copyright 2015, Yahoo! Inc. * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. @@ -11146,7 +11295,7 @@ module.exports = function hoistNonReactStatics(targetComponent, sourceComponent, return targetComponent; }; -},{}],25:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ (function (process){ /** * Copyright 2013-2015, Facebook, Inc. @@ -11202,7 +11351,7 @@ module.exports = invariant; }).call(this,require('_process')) -},{"_process":37}],26:[function(require,module,exports){ +},{"_process":40}],29:[function(require,module,exports){ /** * lodash 3.9.1 (Custom Build) <https://lodash.com/> * Build: `lodash modern modularize exports="npm" -o ./` @@ -11341,7 +11490,7 @@ function isNative(value) { module.exports = getNative; -},{}],27:[function(require,module,exports){ +},{}],30:[function(require,module,exports){ /** * lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` @@ -11734,7 +11883,7 @@ function toNumber(value) { module.exports = debounce; -},{}],28:[function(require,module,exports){ +},{}],31:[function(require,module,exports){ /** * lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` @@ -11993,7 +12142,7 @@ function isObjectLike(value) { module.exports = isArguments; -},{}],29:[function(require,module,exports){ +},{}],32:[function(require,module,exports){ /** * lodash 3.0.4 (Custom Build) <https://lodash.com/> * Build: `lodash modern modularize exports="npm" -o ./` @@ -12175,7 +12324,7 @@ function isNative(value) { module.exports = isArray; -},{}],30:[function(require,module,exports){ +},{}],33:[function(require,module,exports){ /** * lodash 3.1.2 (Custom Build) <https://lodash.com/> * Build: `lodash modern modularize exports="npm" -o ./` @@ -12413,7 +12562,7 @@ function keysIn(object) { module.exports = keys; -},{"lodash._getnative":26,"lodash.isarguments":28,"lodash.isarray":29}],31:[function(require,module,exports){ +},{"lodash._getnative":29,"lodash.isarguments":31,"lodash.isarray":32}],34:[function(require,module,exports){ var overArg = require('./_overArg'); /* Built-in method references for those with the same name as other `lodash` methods. */ @@ -12430,7 +12579,7 @@ var getPrototype = overArg(nativeGetPrototype, Object); module.exports = getPrototype; -},{"./_overArg":33}],32:[function(require,module,exports){ +},{"./_overArg":36}],35:[function(require,module,exports){ /** * Checks if `value` is a host object in IE < 9. * @@ -12452,7 +12601,7 @@ function isHostObject(value) { module.exports = isHostObject; -},{}],33:[function(require,module,exports){ +},{}],36:[function(require,module,exports){ /** * Creates a function that invokes `func` with its first argument transformed. * @@ -12469,7 +12618,7 @@ function overArg(func, transform) { module.exports = overArg; -},{}],34:[function(require,module,exports){ +},{}],37:[function(require,module,exports){ /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". @@ -12500,7 +12649,7 @@ function isObjectLike(value) { module.exports = isObjectLike; -},{}],35:[function(require,module,exports){ +},{}],38:[function(require,module,exports){ var getPrototype = require('./_getPrototype'), isHostObject = require('./_isHostObject'), isObjectLike = require('./isObjectLike'); @@ -12572,7 +12721,7 @@ function isPlainObject(value) { module.exports = isPlainObject; -},{"./_getPrototype":31,"./_isHostObject":32,"./isObjectLike":34}],36:[function(require,module,exports){ +},{"./_getPrototype":34,"./_isHostObject":35,"./isObjectLike":37}],39:[function(require,module,exports){ 'use strict'; /* eslint-disable no-unused-vars */ var hasOwnProperty = Object.prototype.hasOwnProperty; @@ -12657,7 +12806,7 @@ module.exports = shouldUseNative() ? Object.assign : function (target, source) { return to; }; -},{}],37:[function(require,module,exports){ +},{}],40:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -12791,7 +12940,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],38:[function(require,module,exports){ +},{}],41:[function(require,module,exports){ 'use strict'; var strictUriEncode = require('strict-uri-encode'); @@ -12859,7 +13008,7 @@ exports.stringify = function (obj) { }).join('&') : ''; }; -},{"strict-uri-encode":258}],39:[function(require,module,exports){ +},{"strict-uri-encode":224}],42:[function(require,module,exports){ (function (process){ 'use strict'; @@ -12941,7 +13090,7 @@ Provider.childContextTypes = { }; }).call(this,require('_process')) -},{"../utils/storeShape":42,"../utils/warning":43,"_process":37,"react":"react"}],40:[function(require,module,exports){ +},{"../utils/storeShape":45,"../utils/warning":46,"_process":40,"react":"react"}],43:[function(require,module,exports){ (function (process){ 'use strict'; @@ -13338,7 +13487,7 @@ function connect(mapStateToProps, mapDispatchToProps, mergeProps) { } }).call(this,require('_process')) -},{"../utils/shallowEqual":41,"../utils/storeShape":42,"../utils/warning":43,"../utils/wrapActionCreators":44,"_process":37,"hoist-non-react-statics":24,"invariant":25,"lodash/isPlainObject":35,"react":"react"}],41:[function(require,module,exports){ +},{"../utils/shallowEqual":44,"../utils/storeShape":45,"../utils/warning":46,"../utils/wrapActionCreators":47,"_process":40,"hoist-non-react-statics":27,"invariant":28,"lodash/isPlainObject":38,"react":"react"}],44:[function(require,module,exports){ "use strict"; exports.__esModule = true; @@ -13365,7 +13514,7 @@ function shallowEqual(objA, objB) { return true; } -},{}],42:[function(require,module,exports){ +},{}],45:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -13377,7 +13526,7 @@ exports["default"] = _react.PropTypes.shape({ dispatch: _react.PropTypes.func.isRequired, getState: _react.PropTypes.func.isRequired }); -},{"react":"react"}],43:[function(require,module,exports){ +},{"react":"react"}],46:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -13402,7 +13551,7 @@ function warning(message) { } catch (e) {} /* eslint-enable no-empty */ } -},{}],44:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -13415,3168 +13564,7 @@ function wrapActionCreators(actionCreators) { return (0, _redux.bindActionCreators)(actionCreators, dispatch); }; } -},{"redux":"redux"}],45:[function(require,module,exports){ -"use strict"; - -exports.__esModule = true; -exports.loopAsync = loopAsync; -exports.mapAsync = mapAsync; -function loopAsync(turns, work, callback) { - var currentTurn = 0, - isDone = false; - var sync = false, - hasNext = false, - doneArgs = void 0; - - function done() { - isDone = true; - if (sync) { - // Iterate instead of recursing if possible. - doneArgs = [].concat(Array.prototype.slice.call(arguments)); - return; - } - - callback.apply(this, arguments); - } - - function next() { - if (isDone) { - return; - } - - hasNext = true; - if (sync) { - // Iterate instead of recursing if possible. - return; - } - - sync = true; - - while (!isDone && currentTurn < turns && hasNext) { - hasNext = false; - work.call(this, currentTurn++, next, done); - } - - sync = false; - - if (isDone) { - // This means the loop finished synchronously. - callback.apply(this, doneArgs); - return; - } - - if (currentTurn >= turns && hasNext) { - isDone = true; - callback(); - } - } - - next(); -} - -function mapAsync(array, work, callback) { - var length = array.length; - var values = []; - - if (length === 0) return callback(null, values); - - var isDone = false, - doneCount = 0; - - function done(index, error, value) { - if (isDone) return; - - if (error) { - isDone = true; - callback(error); - } else { - values[index] = value; - - isDone = ++doneCount === length; - - if (isDone) callback(null, values); - } - } - - array.forEach(function (item, index) { - work(item, index, function (error, value) { - done(index, error, value); - }); - }); -} -},{}],46:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _InternalPropTypes = require('./InternalPropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/** - * A mixin that adds the "history" instance variable to components. - */ -var History = { - - contextTypes: { - history: _InternalPropTypes.history - }, - - componentWillMount: function componentWillMount() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'the `History` mixin is deprecated, please access `context.router` with your own `contextTypes`. http://tiny.cc/router-historymixin') : void 0; - this.history = this.context.history; - } -}; - -exports.default = History; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./routerWarning":78,"_process":37}],47:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _Link = require('./Link'); - -var _Link2 = _interopRequireDefault(_Link); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/** - * An <IndexLink> is used to link to an <IndexRoute>. - */ -var IndexLink = _react2.default.createClass({ - displayName: 'IndexLink', - render: function render() { - return _react2.default.createElement(_Link2.default, _extends({}, this.props, { onlyActiveOnIndex: true })); - } -}); - -exports.default = IndexLink; -module.exports = exports['default']; -},{"./Link":52,"react":"react"}],48:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _Redirect = require('./Redirect'); - -var _Redirect2 = _interopRequireDefault(_Redirect); - -var _InternalPropTypes = require('./InternalPropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var _React$PropTypes = _react2.default.PropTypes; -var string = _React$PropTypes.string; -var object = _React$PropTypes.object; - -/** - * An <IndexRedirect> is used to redirect from an indexRoute. - */ - -var IndexRedirect = _react2.default.createClass({ - displayName: 'IndexRedirect', - - - statics: { - createRouteFromReactElement: function createRouteFromReactElement(element, parentRoute) { - /* istanbul ignore else: sanity check */ - if (parentRoute) { - parentRoute.indexRoute = _Redirect2.default.createRouteFromReactElement(element); - } else { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'An <IndexRedirect> does not make sense at the root of your route config') : void 0; - } - } - }, - - propTypes: { - to: string.isRequired, - query: object, - state: object, - onEnter: _InternalPropTypes.falsy, - children: _InternalPropTypes.falsy - }, - - /* istanbul ignore next: sanity check */ - render: function render() { - !false ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, '<IndexRedirect> elements are for router configuration only and should not be rendered') : (0, _invariant2.default)(false) : void 0; - } -}); - -exports.default = IndexRedirect; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./Redirect":55,"./routerWarning":78,"_process":37,"invariant":25,"react":"react"}],49:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _RouteUtils = require('./RouteUtils'); - -var _InternalPropTypes = require('./InternalPropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var func = _react2.default.PropTypes.func; - -/** - * An <IndexRoute> is used to specify its parent's <Route indexRoute> in - * a JSX route config. - */ - -var IndexRoute = _react2.default.createClass({ - displayName: 'IndexRoute', - - - statics: { - createRouteFromReactElement: function createRouteFromReactElement(element, parentRoute) { - /* istanbul ignore else: sanity check */ - if (parentRoute) { - parentRoute.indexRoute = (0, _RouteUtils.createRouteFromReactElement)(element); - } else { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'An <IndexRoute> does not make sense at the root of your route config') : void 0; - } - } - }, - - propTypes: { - path: _InternalPropTypes.falsy, - component: _InternalPropTypes.component, - components: _InternalPropTypes.components, - getComponent: func, - getComponents: func - }, - - /* istanbul ignore next: sanity check */ - render: function render() { - !false ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, '<IndexRoute> elements are for router configuration only and should not be rendered') : (0, _invariant2.default)(false) : void 0; - } -}); - -exports.default = IndexRoute; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./RouteUtils":58,"./routerWarning":78,"_process":37,"invariant":25,"react":"react"}],50:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.routes = exports.route = exports.components = exports.component = exports.history = undefined; -exports.falsy = falsy; - -var _react = require('react'); - -var func = _react.PropTypes.func; -var object = _react.PropTypes.object; -var arrayOf = _react.PropTypes.arrayOf; -var oneOfType = _react.PropTypes.oneOfType; -var element = _react.PropTypes.element; -var shape = _react.PropTypes.shape; -var string = _react.PropTypes.string; -function falsy(props, propName, componentName) { - if (props[propName]) return new Error('<' + componentName + '> should not have a "' + propName + '" prop'); -} - -var history = exports.history = shape({ - listen: func.isRequired, - push: func.isRequired, - replace: func.isRequired, - go: func.isRequired, - goBack: func.isRequired, - goForward: func.isRequired -}); - -var component = exports.component = oneOfType([func, string]); -var components = exports.components = oneOfType([component, object]); -var route = exports.route = oneOfType([object, element]); -var routes = exports.routes = oneOfType([route, arrayOf(route)]); -},{"react":"react"}],51:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var object = _react2.default.PropTypes.object; - -/** - * The Lifecycle mixin adds the routerWillLeave lifecycle method to a - * component that may be used to cancel a transition or prompt the user - * for confirmation. - * - * On standard transitions, routerWillLeave receives a single argument: the - * location we're transitioning to. To cancel the transition, return false. - * To prompt the user for confirmation, return a prompt message (string). - * - * During the beforeunload event (assuming you're using the useBeforeUnload - * history enhancer), routerWillLeave does not receive a location object - * because it isn't possible for us to know the location we're transitioning - * to. In this case routerWillLeave must return a prompt message to prevent - * the user from closing the window/tab. - */ - -var Lifecycle = { - - contextTypes: { - history: object.isRequired, - // Nested children receive the route as context, either - // set by the route component using the RouteContext mixin - // or by some other ancestor. - route: object - }, - - propTypes: { - // Route components receive the route object as a prop. - route: object - }, - - componentDidMount: function componentDidMount() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'the `Lifecycle` mixin is deprecated, please use `context.router.setRouteLeaveHook(route, hook)`. http://tiny.cc/router-lifecyclemixin') : void 0; - !this.routerWillLeave ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'The Lifecycle mixin requires you to define a routerWillLeave method') : (0, _invariant2.default)(false) : void 0; - - var route = this.props.route || this.context.route; - - !route ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'The Lifecycle mixin must be used on either a) a <Route component> or ' + 'b) a descendant of a <Route component> that uses the RouteContext mixin') : (0, _invariant2.default)(false) : void 0; - - this._unlistenBeforeLeavingRoute = this.context.history.listenBeforeLeavingRoute(route, this.routerWillLeave); - }, - componentWillUnmount: function componentWillUnmount() { - if (this._unlistenBeforeLeavingRoute) this._unlistenBeforeLeavingRoute(); - } -}; - -exports.default = Lifecycle; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./routerWarning":78,"_process":37,"invariant":25,"react":"react"}],52:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _PropTypes = require('./PropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var _React$PropTypes = _react2.default.PropTypes; -var bool = _React$PropTypes.bool; -var object = _React$PropTypes.object; -var string = _React$PropTypes.string; -var func = _React$PropTypes.func; -var oneOfType = _React$PropTypes.oneOfType; - - -function isLeftClickEvent(event) { - return event.button === 0; -} - -function isModifiedEvent(event) { - return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); -} - -// TODO: De-duplicate against hasAnyProperties in createTransitionManager. -function isEmptyObject(object) { - for (var p in object) { - if (Object.prototype.hasOwnProperty.call(object, p)) return false; - }return true; -} - -function createLocationDescriptor(to, _ref) { - var query = _ref.query; - var hash = _ref.hash; - var state = _ref.state; - - if (query || hash || state) { - return { pathname: to, query: query, hash: hash, state: state }; - } - - return to; -} - -/** - * A <Link> is used to create an <a> element that links to a route. - * When that route is active, the link gets the value of its - * activeClassName prop. - * - * For example, assuming you have the following route: - * - * <Route path="/posts/:postID" component={Post} /> - * - * You could use the following component to link to that route: - * - * <Link to={`/posts/${post.id}`} /> - * - * Links may pass along location state and/or query string parameters - * in the state/query props, respectively. - * - * <Link ... query={{ show: true }} state={{ the: 'state' }} /> - */ -var Link = _react2.default.createClass({ - displayName: 'Link', - - - contextTypes: { - router: _PropTypes.routerShape - }, - - propTypes: { - to: oneOfType([string, object]).isRequired, - query: object, - hash: string, - state: object, - activeStyle: object, - activeClassName: string, - onlyActiveOnIndex: bool.isRequired, - onClick: func, - target: string - }, - - getDefaultProps: function getDefaultProps() { - return { - onlyActiveOnIndex: false, - style: {} - }; - }, - handleClick: function handleClick(event) { - if (this.props.onClick) this.props.onClick(event); - - if (event.defaultPrevented) return; - - !this.context.router ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, '<Link>s rendered outside of a router context cannot navigate.') : (0, _invariant2.default)(false) : void 0; - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) return; - - // If target prop is set (e.g. to "_blank"), let browser handle link. - /* istanbul ignore if: untestable with Karma */ - if (this.props.target) return; - - event.preventDefault(); - - var _props = this.props; - var to = _props.to; - var query = _props.query; - var hash = _props.hash; - var state = _props.state; - - var location = createLocationDescriptor(to, { query: query, hash: hash, state: state }); - - this.context.router.push(location); - }, - render: function render() { - var _props2 = this.props; - var to = _props2.to; - var query = _props2.query; - var hash = _props2.hash; - var state = _props2.state; - var activeClassName = _props2.activeClassName; - var activeStyle = _props2.activeStyle; - var onlyActiveOnIndex = _props2.onlyActiveOnIndex; - - var props = _objectWithoutProperties(_props2, ['to', 'query', 'hash', 'state', 'activeClassName', 'activeStyle', 'onlyActiveOnIndex']); - - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(!(query || hash || state), 'the `query`, `hash`, and `state` props on `<Link>` are deprecated, use `<Link to={{ pathname, query, hash, state }}/>. http://tiny.cc/router-isActivedeprecated') : void 0; - - // Ignore if rendered outside the context of router, simplifies unit testing. - var router = this.context.router; - - - if (router) { - var location = createLocationDescriptor(to, { query: query, hash: hash, state: state }); - props.href = router.createHref(location); - - if (activeClassName || activeStyle != null && !isEmptyObject(activeStyle)) { - if (router.isActive(location, onlyActiveOnIndex)) { - if (activeClassName) { - if (props.className) { - props.className += ' ' + activeClassName; - } else { - props.className = activeClassName; - } - } - - if (activeStyle) props.style = _extends({}, props.style, activeStyle); - } - } - } - - return _react2.default.createElement('a', _extends({}, props, { onClick: this.handleClick })); - } -}); - -exports.default = Link; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./PropTypes":54,"./routerWarning":78,"_process":37,"invariant":25,"react":"react"}],53:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; -exports.compilePattern = compilePattern; -exports.matchPattern = matchPattern; -exports.getParamNames = getParamNames; -exports.getParams = getParams; -exports.formatPattern = formatPattern; - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -function _compilePattern(pattern) { - var regexpSource = ''; - var paramNames = []; - var tokens = []; - - var match = void 0, - lastIndex = 0, - matcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*\*|\*|\(|\)/g; - while (match = matcher.exec(pattern)) { - if (match.index !== lastIndex) { - tokens.push(pattern.slice(lastIndex, match.index)); - regexpSource += escapeRegExp(pattern.slice(lastIndex, match.index)); - } - - if (match[1]) { - regexpSource += '([^/]+)'; - paramNames.push(match[1]); - } else if (match[0] === '**') { - regexpSource += '(.*)'; - paramNames.push('splat'); - } else if (match[0] === '*') { - regexpSource += '(.*?)'; - paramNames.push('splat'); - } else if (match[0] === '(') { - regexpSource += '(?:'; - } else if (match[0] === ')') { - regexpSource += ')?'; - } - - tokens.push(match[0]); - - lastIndex = matcher.lastIndex; - } - - if (lastIndex !== pattern.length) { - tokens.push(pattern.slice(lastIndex, pattern.length)); - regexpSource += escapeRegExp(pattern.slice(lastIndex, pattern.length)); - } - - return { - pattern: pattern, - regexpSource: regexpSource, - paramNames: paramNames, - tokens: tokens - }; -} - -var CompiledPatternsCache = Object.create(null); - -function compilePattern(pattern) { - if (!CompiledPatternsCache[pattern]) CompiledPatternsCache[pattern] = _compilePattern(pattern); - - return CompiledPatternsCache[pattern]; -} - -/** - * Attempts to match a pattern on the given pathname. Patterns may use - * the following special characters: - * - * - :paramName Matches a URL segment up to the next /, ?, or #. The - * captured string is considered a "param" - * - () Wraps a segment of the URL that is optional - * - * Consumes (non-greedy) all characters up to the next - * character in the pattern, or to the end of the URL if - * there is none - * - ** Consumes (greedy) all characters up to the next character - * in the pattern, or to the end of the URL if there is none - * - * The function calls callback(error, matched) when finished. - * The return value is an object with the following properties: - * - * - remainingPathname - * - paramNames - * - paramValues - */ -function matchPattern(pattern, pathname) { - // Ensure pattern starts with leading slash for consistency with pathname. - if (pattern.charAt(0) !== '/') { - pattern = '/' + pattern; - } - - var _compilePattern2 = compilePattern(pattern); - - var regexpSource = _compilePattern2.regexpSource; - var paramNames = _compilePattern2.paramNames; - var tokens = _compilePattern2.tokens; - - - if (pattern.charAt(pattern.length - 1) !== '/') { - regexpSource += '/?'; // Allow optional path separator at end. - } - - // Special-case patterns like '*' for catch-all routes. - if (tokens[tokens.length - 1] === '*') { - regexpSource += '$'; - } - - var match = pathname.match(new RegExp('^' + regexpSource, 'i')); - if (match == null) { - return null; - } - - var matchedPath = match[0]; - var remainingPathname = pathname.substr(matchedPath.length); - - if (remainingPathname) { - // Require that the match ends at a path separator, if we didn't match - // the full path, so any remaining pathname is a new path segment. - if (matchedPath.charAt(matchedPath.length - 1) !== '/') { - return null; - } - - // If there is a remaining pathname, treat the path separator as part of - // the remaining pathname for properly continuing the match. - remainingPathname = '/' + remainingPathname; - } - - return { - remainingPathname: remainingPathname, - paramNames: paramNames, - paramValues: match.slice(1).map(function (v) { - return v && decodeURIComponent(v); - }) - }; -} - -function getParamNames(pattern) { - return compilePattern(pattern).paramNames; -} - -function getParams(pattern, pathname) { - var match = matchPattern(pattern, pathname); - if (!match) { - return null; - } - - var paramNames = match.paramNames; - var paramValues = match.paramValues; - - var params = {}; - - paramNames.forEach(function (paramName, index) { - params[paramName] = paramValues[index]; - }); - - return params; -} - -/** - * Returns a version of the given pattern with params interpolated. Throws - * if there is a dynamic segment of the pattern for which there is no param. - */ -function formatPattern(pattern, params) { - params = params || {}; - - var _compilePattern3 = compilePattern(pattern); - - var tokens = _compilePattern3.tokens; - - var parenCount = 0, - pathname = '', - splatIndex = 0; - - var token = void 0, - paramName = void 0, - paramValue = void 0; - for (var i = 0, len = tokens.length; i < len; ++i) { - token = tokens[i]; - - if (token === '*' || token === '**') { - paramValue = Array.isArray(params.splat) ? params.splat[splatIndex++] : params.splat; - - !(paramValue != null || parenCount > 0) ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'Missing splat #%s for path "%s"', splatIndex, pattern) : (0, _invariant2.default)(false) : void 0; - - if (paramValue != null) pathname += encodeURI(paramValue); - } else if (token === '(') { - parenCount += 1; - } else if (token === ')') { - parenCount -= 1; - } else if (token.charAt(0) === ':') { - paramName = token.substring(1); - paramValue = params[paramName]; - - !(paramValue != null || parenCount > 0) ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'Missing "%s" parameter for path "%s"', paramName, pattern) : (0, _invariant2.default)(false) : void 0; - - if (paramValue != null) pathname += encodeURIComponent(paramValue); - } else { - pathname += token; - } - } - - return pathname.replace(/\/+/g, '/'); -} -}).call(this,require('_process')) - -},{"_process":37,"invariant":25}],54:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; -exports.router = exports.routes = exports.route = exports.components = exports.component = exports.location = exports.history = exports.falsy = exports.locationShape = exports.routerShape = undefined; - -var _react = require('react'); - -var _deprecateObjectProperties = require('./deprecateObjectProperties'); - -var _deprecateObjectProperties2 = _interopRequireDefault(_deprecateObjectProperties); - -var _InternalPropTypes = require('./InternalPropTypes'); - -var InternalPropTypes = _interopRequireWildcard(_InternalPropTypes); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var func = _react.PropTypes.func; -var object = _react.PropTypes.object; -var shape = _react.PropTypes.shape; -var string = _react.PropTypes.string; -var routerShape = exports.routerShape = shape({ - push: func.isRequired, - replace: func.isRequired, - go: func.isRequired, - goBack: func.isRequired, - goForward: func.isRequired, - setRouteLeaveHook: func.isRequired, - isActive: func.isRequired -}); - -var locationShape = exports.locationShape = shape({ - pathname: string.isRequired, - search: string.isRequired, - state: object, - action: string.isRequired, - key: string -}); - -// Deprecated stuff below: - -var falsy = exports.falsy = InternalPropTypes.falsy; -var history = exports.history = InternalPropTypes.history; -var location = exports.location = locationShape; -var component = exports.component = InternalPropTypes.component; -var components = exports.components = InternalPropTypes.components; -var route = exports.route = InternalPropTypes.route; -var routes = exports.routes = InternalPropTypes.routes; -var router = exports.router = routerShape; - -if (process.env.NODE_ENV !== 'production') { - (function () { - var deprecatePropType = function deprecatePropType(propType, message) { - return function () { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, message) : void 0; - return propType.apply(undefined, arguments); - }; - }; - - var deprecateInternalPropType = function deprecateInternalPropType(propType) { - return deprecatePropType(propType, 'This prop type is not intended for external use, and was previously exported by mistake. These internal prop types are deprecated for external use, and will be removed in a later version.'); - }; - - var deprecateRenamedPropType = function deprecateRenamedPropType(propType, name) { - return deprecatePropType(propType, 'The `' + name + '` prop type is now exported as `' + name + 'Shape` to avoid name conflicts. This export is deprecated and will be removed in a later version.'); - }; - - exports.falsy = falsy = deprecateInternalPropType(falsy); - exports.history = history = deprecateInternalPropType(history); - exports.component = component = deprecateInternalPropType(component); - exports.components = components = deprecateInternalPropType(components); - exports.route = route = deprecateInternalPropType(route); - exports.routes = routes = deprecateInternalPropType(routes); - - exports.location = location = deprecateRenamedPropType(location, 'location'); - exports.router = router = deprecateRenamedPropType(router, 'router'); - })(); -} - -var defaultExport = { - falsy: falsy, - history: history, - location: location, - component: component, - components: components, - route: route, - // For some reason, routes was never here. - router: router -}; - -if (process.env.NODE_ENV !== 'production') { - defaultExport = (0, _deprecateObjectProperties2.default)(defaultExport, 'The default export from `react-router/lib/PropTypes` is deprecated. Please use the named exports instead.'); -} - -exports.default = defaultExport; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./deprecateObjectProperties":70,"./routerWarning":78,"_process":37,"react":"react"}],55:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _RouteUtils = require('./RouteUtils'); - -var _PatternUtils = require('./PatternUtils'); - -var _InternalPropTypes = require('./InternalPropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var _React$PropTypes = _react2.default.PropTypes; -var string = _React$PropTypes.string; -var object = _React$PropTypes.object; - -/** - * A <Redirect> is used to declare another URL path a client should - * be sent to when they request a given URL. - * - * Redirects are placed alongside routes in the route configuration - * and are traversed in the same manner. - */ - -var Redirect = _react2.default.createClass({ - displayName: 'Redirect', - - - statics: { - createRouteFromReactElement: function createRouteFromReactElement(element) { - var route = (0, _RouteUtils.createRouteFromReactElement)(element); - - if (route.from) route.path = route.from; - - route.onEnter = function (nextState, replace) { - var location = nextState.location; - var params = nextState.params; - - - var pathname = void 0; - if (route.to.charAt(0) === '/') { - pathname = (0, _PatternUtils.formatPattern)(route.to, params); - } else if (!route.to) { - pathname = location.pathname; - } else { - var routeIndex = nextState.routes.indexOf(route); - var parentPattern = Redirect.getRoutePattern(nextState.routes, routeIndex - 1); - var pattern = parentPattern.replace(/\/*$/, '/') + route.to; - pathname = (0, _PatternUtils.formatPattern)(pattern, params); - } - - replace({ - pathname: pathname, - query: route.query || location.query, - state: route.state || location.state - }); - }; - - return route; - }, - getRoutePattern: function getRoutePattern(routes, routeIndex) { - var parentPattern = ''; - - for (var i = routeIndex; i >= 0; i--) { - var route = routes[i]; - var pattern = route.path || ''; - - parentPattern = pattern.replace(/\/*$/, '/') + parentPattern; - - if (pattern.indexOf('/') === 0) break; - } - - return '/' + parentPattern; - } - }, - - propTypes: { - path: string, - from: string, // Alias for path - to: string.isRequired, - query: object, - state: object, - onEnter: _InternalPropTypes.falsy, - children: _InternalPropTypes.falsy - }, - - /* istanbul ignore next: sanity check */ - render: function render() { - !false ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, '<Redirect> elements are for router configuration only and should not be rendered') : (0, _invariant2.default)(false) : void 0; - } -}); - -exports.default = Redirect; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./PatternUtils":53,"./RouteUtils":58,"_process":37,"invariant":25,"react":"react"}],56:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _RouteUtils = require('./RouteUtils'); - -var _InternalPropTypes = require('./InternalPropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var _React$PropTypes = _react2.default.PropTypes; -var string = _React$PropTypes.string; -var func = _React$PropTypes.func; - -/** - * A <Route> is used to declare which components are rendered to the - * page when the URL matches a given pattern. - * - * Routes are arranged in a nested tree structure. When a new URL is - * requested, the tree is searched depth-first to find a route whose - * path matches the URL. When one is found, all routes in the tree - * that lead to it are considered "active" and their components are - * rendered into the DOM, nested in the same order as in the tree. - */ - -var Route = _react2.default.createClass({ - displayName: 'Route', - - - statics: { - createRouteFromReactElement: _RouteUtils.createRouteFromReactElement - }, - - propTypes: { - path: string, - component: _InternalPropTypes.component, - components: _InternalPropTypes.components, - getComponent: func, - getComponents: func - }, - - /* istanbul ignore next: sanity check */ - render: function render() { - !false ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, '<Route> elements are for router configuration only and should not be rendered') : (0, _invariant2.default)(false) : void 0; - } -}); - -exports.default = Route; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./RouteUtils":58,"_process":37,"invariant":25,"react":"react"}],57:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var object = _react2.default.PropTypes.object; - -/** - * The RouteContext mixin provides a convenient way for route - * components to set the route in context. This is needed for - * routes that render elements that want to use the Lifecycle - * mixin to prevent transitions. - */ - -var RouteContext = { - - propTypes: { - route: object.isRequired - }, - - childContextTypes: { - route: object.isRequired - }, - - getChildContext: function getChildContext() { - return { - route: this.props.route - }; - }, - componentWillMount: function componentWillMount() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'The `RouteContext` mixin is deprecated. You can provide `this.props.route` on context with your own `contextTypes`. http://tiny.cc/router-routecontextmixin') : void 0; - } -}; - -exports.default = RouteContext; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./routerWarning":78,"_process":37,"react":"react"}],58:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.isReactChildren = isReactChildren; -exports.createRouteFromReactElement = createRouteFromReactElement; -exports.createRoutesFromReactChildren = createRoutesFromReactChildren; -exports.createRoutes = createRoutes; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isValidChild(object) { - return object == null || _react2.default.isValidElement(object); -} - -function isReactChildren(object) { - return isValidChild(object) || Array.isArray(object) && object.every(isValidChild); -} - -function createRoute(defaultProps, props) { - return _extends({}, defaultProps, props); -} - -function createRouteFromReactElement(element) { - var type = element.type; - var route = createRoute(type.defaultProps, element.props); - - if (route.children) { - var childRoutes = createRoutesFromReactChildren(route.children, route); - - if (childRoutes.length) route.childRoutes = childRoutes; - - delete route.children; - } - - return route; -} - -/** - * Creates and returns a routes object from the given ReactChildren. JSX - * provides a convenient way to visualize how routes in the hierarchy are - * nested. - * - * import { Route, createRoutesFromReactChildren } from 'react-router' - * - * const routes = createRoutesFromReactChildren( - * <Route component={App}> - * <Route path="home" component={Dashboard}/> - * <Route path="news" component={NewsFeed}/> - * </Route> - * ) - * - * Note: This method is automatically used when you provide <Route> children - * to a <Router> component. - */ -function createRoutesFromReactChildren(children, parentRoute) { - var routes = []; - - _react2.default.Children.forEach(children, function (element) { - if (_react2.default.isValidElement(element)) { - // Component classes may have a static create* method. - if (element.type.createRouteFromReactElement) { - var route = element.type.createRouteFromReactElement(element, parentRoute); - - if (route) routes.push(route); - } else { - routes.push(createRouteFromReactElement(element)); - } - } - }); - - return routes; -} - -/** - * Creates and returns an array of routes from the given object which - * may be a JSX route, a plain object route, or an array of either. - */ -function createRoutes(routes) { - if (isReactChildren(routes)) { - routes = createRoutesFromReactChildren(routes); - } else if (routes && !Array.isArray(routes)) { - routes = [routes]; - } - - return routes; -} -},{"react":"react"}],59:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _createHashHistory = require('history/lib/createHashHistory'); - -var _createHashHistory2 = _interopRequireDefault(_createHashHistory); - -var _useQueries = require('history/lib/useQueries'); - -var _useQueries2 = _interopRequireDefault(_useQueries); - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _createTransitionManager = require('./createTransitionManager'); - -var _createTransitionManager2 = _interopRequireDefault(_createTransitionManager); - -var _InternalPropTypes = require('./InternalPropTypes'); - -var _RouterContext = require('./RouterContext'); - -var _RouterContext2 = _interopRequireDefault(_RouterContext); - -var _RouteUtils = require('./RouteUtils'); - -var _RouterUtils = require('./RouterUtils'); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -function isDeprecatedHistory(history) { - return !history || !history.__v2_compatible__; -} - -/* istanbul ignore next: sanity check */ -function isUnsupportedHistory(history) { - // v3 histories expose getCurrentLocation, but aren't currently supported. - return history && history.getCurrentLocation; -} - -var _React$PropTypes = _react2.default.PropTypes; -var func = _React$PropTypes.func; -var object = _React$PropTypes.object; - -/** - * A <Router> is a high-level API for automatically setting up - * a router that renders a <RouterContext> with all the props - * it needs each time the URL changes. - */ - -var Router = _react2.default.createClass({ - displayName: 'Router', - - - propTypes: { - history: object, - children: _InternalPropTypes.routes, - routes: _InternalPropTypes.routes, // alias for children - render: func, - createElement: func, - onError: func, - onUpdate: func, - - // Deprecated: - parseQueryString: func, - stringifyQuery: func, - - // PRIVATE: For client-side rehydration of server match. - matchContext: object - }, - - getDefaultProps: function getDefaultProps() { - return { - render: function render(props) { - return _react2.default.createElement(_RouterContext2.default, props); - } - }; - }, - getInitialState: function getInitialState() { - return { - location: null, - routes: null, - params: null, - components: null - }; - }, - handleError: function handleError(error) { - if (this.props.onError) { - this.props.onError.call(this, error); - } else { - // Throw errors by default so we don't silently swallow them! - throw error; // This error probably occurred in getChildRoutes or getComponents. - } - }, - componentWillMount: function componentWillMount() { - var _this = this; - - var _props = this.props; - var parseQueryString = _props.parseQueryString; - var stringifyQuery = _props.stringifyQuery; - - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(!(parseQueryString || stringifyQuery), '`parseQueryString` and `stringifyQuery` are deprecated. Please create a custom history. http://tiny.cc/router-customquerystring') : void 0; - - var _createRouterObjects = this.createRouterObjects(); - - var history = _createRouterObjects.history; - var transitionManager = _createRouterObjects.transitionManager; - var router = _createRouterObjects.router; - - - this._unlisten = transitionManager.listen(function (error, state) { - if (error) { - _this.handleError(error); - } else { - _this.setState(state, _this.props.onUpdate); - } - }); - - this.history = history; - this.router = router; - }, - createRouterObjects: function createRouterObjects() { - var matchContext = this.props.matchContext; - - if (matchContext) { - return matchContext; - } - - var history = this.props.history; - var _props2 = this.props; - var routes = _props2.routes; - var children = _props2.children; - - - !!isUnsupportedHistory(history) ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'You have provided a history object created with history v3.x. ' + 'This version of React Router is not compatible with v3 history ' + 'objects. Please use history v2.x instead.') : (0, _invariant2.default)(false) : void 0; - - if (isDeprecatedHistory(history)) { - history = this.wrapDeprecatedHistory(history); - } - - var transitionManager = (0, _createTransitionManager2.default)(history, (0, _RouteUtils.createRoutes)(routes || children)); - var router = (0, _RouterUtils.createRouterObject)(history, transitionManager); - var routingHistory = (0, _RouterUtils.createRoutingHistory)(history, transitionManager); - - return { history: routingHistory, transitionManager: transitionManager, router: router }; - }, - wrapDeprecatedHistory: function wrapDeprecatedHistory(history) { - var _props3 = this.props; - var parseQueryString = _props3.parseQueryString; - var stringifyQuery = _props3.stringifyQuery; - - - var createHistory = void 0; - if (history) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'It appears you have provided a deprecated history object to `<Router/>`, please use a history provided by ' + 'React Router with `import { browserHistory } from \'react-router\'` or `import { hashHistory } from \'react-router\'`. ' + 'If you are using a custom history please create it with `useRouterHistory`, see http://tiny.cc/router-usinghistory for details.') : void 0; - createHistory = function createHistory() { - return history; - }; - } else { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`Router` no longer defaults the history prop to hash history. Please use the `hashHistory` singleton instead. http://tiny.cc/router-defaulthistory') : void 0; - createHistory = _createHashHistory2.default; - } - - return (0, _useQueries2.default)(createHistory)({ parseQueryString: parseQueryString, stringifyQuery: stringifyQuery }); - }, - - - /* istanbul ignore next: sanity check */ - componentWillReceiveProps: function componentWillReceiveProps(nextProps) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(nextProps.history === this.props.history, 'You cannot change <Router history>; it will be ignored') : void 0; - - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)((nextProps.routes || nextProps.children) === (this.props.routes || this.props.children), 'You cannot change <Router routes>; it will be ignored') : void 0; - }, - componentWillUnmount: function componentWillUnmount() { - if (this._unlisten) this._unlisten(); - }, - render: function render() { - var _state = this.state; - var location = _state.location; - var routes = _state.routes; - var params = _state.params; - var components = _state.components; - var _props4 = this.props; - var createElement = _props4.createElement; - var render = _props4.render; - - var props = _objectWithoutProperties(_props4, ['createElement', 'render']); - - if (location == null) return null; // Async match - - // Only forward non-Router-specific props to routing context, as those are - // the only ones that might be custom routing context props. - Object.keys(Router.propTypes).forEach(function (propType) { - return delete props[propType]; - }); - - return render(_extends({}, props, { - history: this.history, - router: this.router, - location: location, - routes: routes, - params: params, - components: components, - createElement: createElement - })); - } -}); - -exports.default = Router; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./InternalPropTypes":50,"./RouteUtils":58,"./RouterContext":60,"./RouterUtils":61,"./createTransitionManager":69,"./routerWarning":78,"_process":37,"history/lib/createHashHistory":15,"history/lib/useQueries":22,"invariant":25,"react":"react"}],60:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _deprecateObjectProperties = require('./deprecateObjectProperties'); - -var _deprecateObjectProperties2 = _interopRequireDefault(_deprecateObjectProperties); - -var _getRouteParams = require('./getRouteParams'); - -var _getRouteParams2 = _interopRequireDefault(_getRouteParams); - -var _RouteUtils = require('./RouteUtils'); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var _React$PropTypes = _react2.default.PropTypes; -var array = _React$PropTypes.array; -var func = _React$PropTypes.func; -var object = _React$PropTypes.object; - -/** - * A <RouterContext> renders the component tree for a given router state - * and sets the history object and the current location in context. - */ - -var RouterContext = _react2.default.createClass({ - displayName: 'RouterContext', - - - propTypes: { - history: object, - router: object.isRequired, - location: object.isRequired, - routes: array.isRequired, - params: object.isRequired, - components: array.isRequired, - createElement: func.isRequired - }, - - getDefaultProps: function getDefaultProps() { - return { - createElement: _react2.default.createElement - }; - }, - - - childContextTypes: { - history: object, - location: object.isRequired, - router: object.isRequired - }, - - getChildContext: function getChildContext() { - var _props = this.props; - var router = _props.router; - var history = _props.history; - var location = _props.location; - - if (!router) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`<RouterContext>` expects a `router` rather than a `history`') : void 0; - - router = _extends({}, history, { - setRouteLeaveHook: history.listenBeforeLeavingRoute - }); - delete router.listenBeforeLeavingRoute; - } - - if (process.env.NODE_ENV !== 'production') { - location = (0, _deprecateObjectProperties2.default)(location, '`context.location` is deprecated, please use a route component\'s `props.location` instead. http://tiny.cc/router-accessinglocation'); - } - - return { history: history, location: location, router: router }; - }, - createElement: function createElement(component, props) { - return component == null ? null : this.props.createElement(component, props); - }, - render: function render() { - var _this = this; - - var _props2 = this.props; - var history = _props2.history; - var location = _props2.location; - var routes = _props2.routes; - var params = _props2.params; - var components = _props2.components; - - var element = null; - - if (components) { - element = components.reduceRight(function (element, components, index) { - if (components == null) return element; // Don't create new children; use the grandchildren. - - var route = routes[index]; - var routeParams = (0, _getRouteParams2.default)(route, params); - var props = { - history: history, - location: location, - params: params, - route: route, - routeParams: routeParams, - routes: routes - }; - - if ((0, _RouteUtils.isReactChildren)(element)) { - props.children = element; - } else if (element) { - for (var prop in element) { - if (Object.prototype.hasOwnProperty.call(element, prop)) props[prop] = element[prop]; - } - } - - if ((typeof components === 'undefined' ? 'undefined' : _typeof(components)) === 'object') { - var elements = {}; - - for (var key in components) { - if (Object.prototype.hasOwnProperty.call(components, key)) { - // Pass through the key as a prop to createElement to allow - // custom createElement functions to know which named component - // they're rendering, for e.g. matching up to fetched data. - elements[key] = _this.createElement(components[key], _extends({ - key: key }, props)); - } - } - - return elements; - } - - return _this.createElement(components, props); - }, element); - } - - !(element === null || element === false || _react2.default.isValidElement(element)) ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'The root route must render a single element') : (0, _invariant2.default)(false) : void 0; - - return element; - } -}); - -exports.default = RouterContext; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./RouteUtils":58,"./deprecateObjectProperties":70,"./getRouteParams":72,"./routerWarning":78,"_process":37,"invariant":25,"react":"react"}],61:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.createRouterObject = createRouterObject; -exports.createRoutingHistory = createRoutingHistory; - -var _deprecateObjectProperties = require('./deprecateObjectProperties'); - -var _deprecateObjectProperties2 = _interopRequireDefault(_deprecateObjectProperties); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function createRouterObject(history, transitionManager) { - return _extends({}, history, { - setRouteLeaveHook: transitionManager.listenBeforeLeavingRoute, - isActive: transitionManager.isActive - }); -} - -// deprecated -function createRoutingHistory(history, transitionManager) { - history = _extends({}, history, transitionManager); - - if (process.env.NODE_ENV !== 'production') { - history = (0, _deprecateObjectProperties2.default)(history, '`props.history` and `context.history` are deprecated. Please use `context.router`. http://tiny.cc/router-contextchanges'); - } - - return history; -} -}).call(this,require('_process')) - -},{"./deprecateObjectProperties":70,"_process":37}],62:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _RouterContext = require('./RouterContext'); - -var _RouterContext2 = _interopRequireDefault(_RouterContext); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var RoutingContext = _react2.default.createClass({ - displayName: 'RoutingContext', - componentWillMount: function componentWillMount() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`RoutingContext` has been renamed to `RouterContext`. Please use `import { RouterContext } from \'react-router\'`. http://tiny.cc/router-routercontext') : void 0; - }, - render: function render() { - return _react2.default.createElement(_RouterContext2.default, this.props); - } -}); - -exports.default = RoutingContext; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./RouterContext":60,"./routerWarning":78,"_process":37,"react":"react"}],63:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; -exports.runEnterHooks = runEnterHooks; -exports.runChangeHooks = runChangeHooks; -exports.runLeaveHooks = runLeaveHooks; - -var _AsyncUtils = require('./AsyncUtils'); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function createTransitionHook(hook, route, asyncArity) { - return function () { - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - hook.apply(route, args); - - if (hook.length < asyncArity) { - var callback = args[args.length - 1]; - // Assume hook executes synchronously and - // automatically call the callback. - callback(); - } - }; -} - -function getEnterHooks(routes) { - return routes.reduce(function (hooks, route) { - if (route.onEnter) hooks.push(createTransitionHook(route.onEnter, route, 3)); - - return hooks; - }, []); -} - -function getChangeHooks(routes) { - return routes.reduce(function (hooks, route) { - if (route.onChange) hooks.push(createTransitionHook(route.onChange, route, 4)); - return hooks; - }, []); -} - -function runTransitionHooks(length, iter, callback) { - if (!length) { - callback(); - return; - } - - var redirectInfo = void 0; - function replace(location, deprecatedPathname, deprecatedQuery) { - if (deprecatedPathname) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`replaceState(state, pathname, query) is deprecated; use `replace(location)` with a location descriptor instead. http://tiny.cc/router-isActivedeprecated') : void 0; - redirectInfo = { - pathname: deprecatedPathname, - query: deprecatedQuery, - state: location - }; - - return; - } - - redirectInfo = location; - } - - (0, _AsyncUtils.loopAsync)(length, function (index, next, done) { - iter(index, replace, function (error) { - if (error || redirectInfo) { - done(error, redirectInfo); // No need to continue. - } else { - next(); - } - }); - }, callback); -} - -/** - * Runs all onEnter hooks in the given array of routes in order - * with onEnter(nextState, replace, callback) and calls - * callback(error, redirectInfo) when finished. The first hook - * to use replace short-circuits the loop. - * - * If a hook needs to run asynchronously, it may use the callback - * function. However, doing so will cause the transition to pause, - * which could lead to a non-responsive UI if the hook is slow. - */ -function runEnterHooks(routes, nextState, callback) { - var hooks = getEnterHooks(routes); - return runTransitionHooks(hooks.length, function (index, replace, next) { - hooks[index](nextState, replace, next); - }, callback); -} - -/** - * Runs all onChange hooks in the given array of routes in order - * with onChange(prevState, nextState, replace, callback) and calls - * callback(error, redirectInfo) when finished. The first hook - * to use replace short-circuits the loop. - * - * If a hook needs to run asynchronously, it may use the callback - * function. However, doing so will cause the transition to pause, - * which could lead to a non-responsive UI if the hook is slow. - */ -function runChangeHooks(routes, state, nextState, callback) { - var hooks = getChangeHooks(routes); - return runTransitionHooks(hooks.length, function (index, replace, next) { - hooks[index](state, nextState, replace, next); - }, callback); -} - -/** - * Runs all onLeave hooks in the given array of routes in order. - */ -function runLeaveHooks(routes, prevState) { - for (var i = 0, len = routes.length; i < len; ++i) { - if (routes[i].onLeave) routes[i].onLeave.call(routes[i], prevState); - } -} -}).call(this,require('_process')) - -},{"./AsyncUtils":45,"./routerWarning":78,"_process":37}],64:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _RouterContext = require('./RouterContext'); - -var _RouterContext2 = _interopRequireDefault(_RouterContext); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = function () { - for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { - middlewares[_key] = arguments[_key]; - } - - var withContext = middlewares.map(function (m) { - return m.renderRouterContext; - }).filter(function (f) { - return f; - }); - var withComponent = middlewares.map(function (m) { - return m.renderRouteComponent; - }).filter(function (f) { - return f; - }); - var makeCreateElement = function makeCreateElement() { - var baseCreateElement = arguments.length <= 0 || arguments[0] === undefined ? _react.createElement : arguments[0]; - return function (Component, props) { - return withComponent.reduceRight(function (previous, renderRouteComponent) { - return renderRouteComponent(previous, props); - }, baseCreateElement(Component, props)); - }; - }; - - return function (renderProps) { - return withContext.reduceRight(function (previous, renderRouterContext) { - return renderRouterContext(previous, renderProps); - }, _react2.default.createElement(_RouterContext2.default, _extends({}, renderProps, { - createElement: makeCreateElement(renderProps.createElement) - }))); - }; -}; - -module.exports = exports['default']; -},{"./RouterContext":60,"react":"react"}],65:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _createBrowserHistory = require('history/lib/createBrowserHistory'); - -var _createBrowserHistory2 = _interopRequireDefault(_createBrowserHistory); - -var _createRouterHistory = require('./createRouterHistory'); - -var _createRouterHistory2 = _interopRequireDefault(_createRouterHistory); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = (0, _createRouterHistory2.default)(_createBrowserHistory2.default); -module.exports = exports['default']; -},{"./createRouterHistory":68,"history/lib/createBrowserHistory":13}],66:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _PatternUtils = require('./PatternUtils'); - -function routeParamsChanged(route, prevState, nextState) { - if (!route.path) return false; - - var paramNames = (0, _PatternUtils.getParamNames)(route.path); - - return paramNames.some(function (paramName) { - return prevState.params[paramName] !== nextState.params[paramName]; - }); -} - -/** - * Returns an object of { leaveRoutes, changeRoutes, enterRoutes } determined by - * the change from prevState to nextState. We leave routes if either - * 1) they are not in the next state or 2) they are in the next state - * but their params have changed (i.e. /users/123 => /users/456). - * - * leaveRoutes are ordered starting at the leaf route of the tree - * we're leaving up to the common parent route. enterRoutes are ordered - * from the top of the tree we're entering down to the leaf route. - * - * changeRoutes are any routes that didn't leave or enter during - * the transition. - */ -function computeChangedRoutes(prevState, nextState) { - var prevRoutes = prevState && prevState.routes; - var nextRoutes = nextState.routes; - - var leaveRoutes = void 0, - changeRoutes = void 0, - enterRoutes = void 0; - if (prevRoutes) { - (function () { - var parentIsLeaving = false; - leaveRoutes = prevRoutes.filter(function (route) { - if (parentIsLeaving) { - return true; - } else { - var isLeaving = nextRoutes.indexOf(route) === -1 || routeParamsChanged(route, prevState, nextState); - if (isLeaving) parentIsLeaving = true; - return isLeaving; - } - }); - - // onLeave hooks start at the leaf route. - leaveRoutes.reverse(); - - enterRoutes = []; - changeRoutes = []; - - nextRoutes.forEach(function (route) { - var isNew = prevRoutes.indexOf(route) === -1; - var paramsChanged = leaveRoutes.indexOf(route) !== -1; - - if (isNew || paramsChanged) enterRoutes.push(route);else changeRoutes.push(route); - }); - })(); - } else { - leaveRoutes = []; - changeRoutes = []; - enterRoutes = nextRoutes; - } - - return { - leaveRoutes: leaveRoutes, - changeRoutes: changeRoutes, - enterRoutes: enterRoutes - }; -} - -exports.default = computeChangedRoutes; -module.exports = exports['default']; -},{"./PatternUtils":53}],67:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.default = createMemoryHistory; - -var _useQueries = require('history/lib/useQueries'); - -var _useQueries2 = _interopRequireDefault(_useQueries); - -var _useBasename = require('history/lib/useBasename'); - -var _useBasename2 = _interopRequireDefault(_useBasename); - -var _createMemoryHistory = require('history/lib/createMemoryHistory'); - -var _createMemoryHistory2 = _interopRequireDefault(_createMemoryHistory); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function createMemoryHistory(options) { - // signatures and type checking differ between `useRoutes` and - // `createMemoryHistory`, have to create `memoryHistory` first because - // `useQueries` doesn't understand the signature - var memoryHistory = (0, _createMemoryHistory2.default)(options); - var createHistory = function createHistory() { - return memoryHistory; - }; - var history = (0, _useQueries2.default)((0, _useBasename2.default)(createHistory))(options); - history.__v2_compatible__ = true; - return history; -} -module.exports = exports['default']; -},{"history/lib/createMemoryHistory":18,"history/lib/useBasename":21,"history/lib/useQueries":22}],68:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -exports.default = function (createHistory) { - var history = void 0; - if (canUseDOM) history = (0, _useRouterHistory2.default)(createHistory)(); - return history; -}; - -var _useRouterHistory = require('./useRouterHistory'); - -var _useRouterHistory2 = _interopRequireDefault(_useRouterHistory); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement); - -module.exports = exports['default']; -},{"./useRouterHistory":79}],69:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.default = createTransitionManager; - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _Actions = require('history/lib/Actions'); - -var _computeChangedRoutes2 = require('./computeChangedRoutes'); - -var _computeChangedRoutes3 = _interopRequireDefault(_computeChangedRoutes2); - -var _TransitionUtils = require('./TransitionUtils'); - -var _isActive2 = require('./isActive'); - -var _isActive3 = _interopRequireDefault(_isActive2); - -var _getComponents = require('./getComponents'); - -var _getComponents2 = _interopRequireDefault(_getComponents); - -var _matchRoutes = require('./matchRoutes'); - -var _matchRoutes2 = _interopRequireDefault(_matchRoutes); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function hasAnyProperties(object) { - for (var p in object) { - if (Object.prototype.hasOwnProperty.call(object, p)) return true; - }return false; -} - -function createTransitionManager(history, routes) { - var state = {}; - - // Signature should be (location, indexOnly), but needs to support (path, - // query, indexOnly) - function isActive(location) { - var indexOnlyOrDeprecatedQuery = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; - var deprecatedIndexOnly = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2]; - - var indexOnly = void 0; - if (indexOnlyOrDeprecatedQuery && indexOnlyOrDeprecatedQuery !== true || deprecatedIndexOnly !== null) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`isActive(pathname, query, indexOnly) is deprecated; use `isActive(location, indexOnly)` with a location descriptor instead. http://tiny.cc/router-isActivedeprecated') : void 0; - location = { pathname: location, query: indexOnlyOrDeprecatedQuery }; - indexOnly = deprecatedIndexOnly || false; - } else { - location = history.createLocation(location); - indexOnly = indexOnlyOrDeprecatedQuery; - } - - return (0, _isActive3.default)(location, indexOnly, state.location, state.routes, state.params); - } - - function createLocationFromRedirectInfo(location) { - return history.createLocation(location, _Actions.REPLACE); - } - - var partialNextState = void 0; - - function match(location, callback) { - if (partialNextState && partialNextState.location === location) { - // Continue from where we left off. - finishMatch(partialNextState, callback); - } else { - (0, _matchRoutes2.default)(routes, location, function (error, nextState) { - if (error) { - callback(error); - } else if (nextState) { - finishMatch(_extends({}, nextState, { location: location }), callback); - } else { - callback(); - } - }); - } - } - - function finishMatch(nextState, callback) { - var _computeChangedRoutes = (0, _computeChangedRoutes3.default)(state, nextState); - - var leaveRoutes = _computeChangedRoutes.leaveRoutes; - var changeRoutes = _computeChangedRoutes.changeRoutes; - var enterRoutes = _computeChangedRoutes.enterRoutes; - - - (0, _TransitionUtils.runLeaveHooks)(leaveRoutes, state); - - // Tear down confirmation hooks for left routes - leaveRoutes.filter(function (route) { - return enterRoutes.indexOf(route) === -1; - }).forEach(removeListenBeforeHooksForRoute); - - // change and enter hooks are run in series - (0, _TransitionUtils.runChangeHooks)(changeRoutes, state, nextState, function (error, redirectInfo) { - if (error || redirectInfo) return handleErrorOrRedirect(error, redirectInfo); - - (0, _TransitionUtils.runEnterHooks)(enterRoutes, nextState, finishEnterHooks); - }); - - function finishEnterHooks(error, redirectInfo) { - if (error || redirectInfo) return handleErrorOrRedirect(error, redirectInfo); - - // TODO: Fetch components after state is updated. - (0, _getComponents2.default)(nextState, function (error, components) { - if (error) { - callback(error); - } else { - // TODO: Make match a pure function and have some other API - // for "match and update state". - callback(null, null, state = _extends({}, nextState, { components: components })); - } - }); - } - - function handleErrorOrRedirect(error, redirectInfo) { - if (error) callback(error);else callback(null, createLocationFromRedirectInfo(redirectInfo)); - } - } - - var RouteGuid = 1; - - function getRouteID(route) { - var create = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; - - return route.__id__ || create && (route.__id__ = RouteGuid++); - } - - var RouteHooks = Object.create(null); - - function getRouteHooksForRoutes(routes) { - return routes.reduce(function (hooks, route) { - hooks.push.apply(hooks, RouteHooks[getRouteID(route)]); - return hooks; - }, []); - } - - function transitionHook(location, callback) { - (0, _matchRoutes2.default)(routes, location, function (error, nextState) { - if (nextState == null) { - // TODO: We didn't actually match anything, but hang - // onto error/nextState so we don't have to matchRoutes - // again in the listen callback. - callback(); - return; - } - - // Cache some state here so we don't have to - // matchRoutes() again in the listen callback. - partialNextState = _extends({}, nextState, { location: location }); - - var hooks = getRouteHooksForRoutes((0, _computeChangedRoutes3.default)(state, partialNextState).leaveRoutes); - - var result = void 0; - for (var i = 0, len = hooks.length; result == null && i < len; ++i) { - // Passing the location arg here indicates to - // the user that this is a transition hook. - result = hooks[i](location); - } - - callback(result); - }); - } - - /* istanbul ignore next: untestable with Karma */ - function beforeUnloadHook() { - // Synchronously check to see if any route hooks want - // to prevent the current window/tab from closing. - if (state.routes) { - var hooks = getRouteHooksForRoutes(state.routes); - - var message = void 0; - for (var i = 0, len = hooks.length; typeof message !== 'string' && i < len; ++i) { - // Passing no args indicates to the user that this is a - // beforeunload hook. We don't know the next location. - message = hooks[i](); - } - - return message; - } - } - - var unlistenBefore = void 0, - unlistenBeforeUnload = void 0; - - function removeListenBeforeHooksForRoute(route) { - var routeID = getRouteID(route, false); - if (!routeID) { - return; - } - - delete RouteHooks[routeID]; - - if (!hasAnyProperties(RouteHooks)) { - // teardown transition & beforeunload hooks - if (unlistenBefore) { - unlistenBefore(); - unlistenBefore = null; - } - - if (unlistenBeforeUnload) { - unlistenBeforeUnload(); - unlistenBeforeUnload = null; - } - } - } - - /** - * Registers the given hook function to run before leaving the given route. - * - * During a normal transition, the hook function receives the next location - * as its only argument and can return either a prompt message (string) to show the user, - * to make sure they want to leave the page; or `false`, to prevent the transition. - * Any other return value will have no effect. - * - * During the beforeunload event (in browsers) the hook receives no arguments. - * In this case it must return a prompt message to prevent the transition. - * - * Returns a function that may be used to unbind the listener. - */ - function listenBeforeLeavingRoute(route, hook) { - // TODO: Warn if they register for a route that isn't currently - // active. They're probably doing something wrong, like re-creating - // route objects on every location change. - var routeID = getRouteID(route); - var hooks = RouteHooks[routeID]; - - if (!hooks) { - var thereWereNoRouteHooks = !hasAnyProperties(RouteHooks); - - RouteHooks[routeID] = [hook]; - - if (thereWereNoRouteHooks) { - // setup transition & beforeunload hooks - unlistenBefore = history.listenBefore(transitionHook); - - if (history.listenBeforeUnload) unlistenBeforeUnload = history.listenBeforeUnload(beforeUnloadHook); - } - } else { - if (hooks.indexOf(hook) === -1) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'adding multiple leave hooks for the same route is deprecated; manage multiple confirmations in your own code instead') : void 0; - - hooks.push(hook); - } - } - - return function () { - var hooks = RouteHooks[routeID]; - - if (hooks) { - var newHooks = hooks.filter(function (item) { - return item !== hook; - }); - - if (newHooks.length === 0) { - removeListenBeforeHooksForRoute(route); - } else { - RouteHooks[routeID] = newHooks; - } - } - }; - } - - /** - * This is the API for stateful environments. As the location - * changes, we update state and call the listener. We can also - * gracefully handle errors and redirects. - */ - function listen(listener) { - // TODO: Only use a single history listener. Otherwise we'll - // end up with multiple concurrent calls to match. - return history.listen(function (location) { - if (state.location === location) { - listener(null, state); - } else { - match(location, function (error, redirectLocation, nextState) { - if (error) { - listener(error); - } else if (redirectLocation) { - history.transitionTo(redirectLocation); - } else if (nextState) { - listener(null, nextState); - } else { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'Location "%s" did not match any routes', location.pathname + location.search + location.hash) : void 0; - } - }); - } - }); - } - - return { - isActive: isActive, - match: match, - listenBeforeLeavingRoute: listenBeforeLeavingRoute, - listen: listen - }; -} - -//export default useRoutes - -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./TransitionUtils":63,"./computeChangedRoutes":66,"./getComponents":71,"./isActive":74,"./matchRoutes":77,"./routerWarning":78,"_process":37,"history/lib/Actions":7}],70:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; -exports.canUseMembrane = undefined; - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var canUseMembrane = exports.canUseMembrane = false; - -// No-op by default. -var deprecateObjectProperties = function deprecateObjectProperties(object) { - return object; -}; - -if (process.env.NODE_ENV !== 'production') { - try { - if (Object.defineProperty({}, 'x', { - get: function get() { - return true; - } - }).x) { - exports.canUseMembrane = canUseMembrane = true; - } - /* eslint-disable no-empty */ - } catch (e) {} - /* eslint-enable no-empty */ - - if (canUseMembrane) { - deprecateObjectProperties = function deprecateObjectProperties(object, message) { - // Wrap the deprecated object in a membrane to warn on property access. - var membrane = {}; - - var _loop = function _loop(prop) { - if (!Object.prototype.hasOwnProperty.call(object, prop)) { - return 'continue'; - } - - if (typeof object[prop] === 'function') { - // Can't use fat arrow here because of use of arguments below. - membrane[prop] = function () { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, message) : void 0; - return object[prop].apply(object, arguments); - }; - return 'continue'; - } - - // These properties are non-enumerable to prevent React dev tools from - // seeing them and causing spurious warnings when accessing them. In - // principle this could be done with a proxy, but support for the - // ownKeys trap on proxies is not universal, even among browsers that - // otherwise support proxies. - Object.defineProperty(membrane, prop, { - get: function get() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, message) : void 0; - return object[prop]; - } - }); - }; - - for (var prop in object) { - var _ret = _loop(prop); - - if (_ret === 'continue') continue; - } - - return membrane; - }; - } -} - -exports.default = deprecateObjectProperties; -}).call(this,require('_process')) - -},{"./routerWarning":78,"_process":37}],71:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _AsyncUtils = require('./AsyncUtils'); - -var _makeStateWithLocation = require('./makeStateWithLocation'); - -var _makeStateWithLocation2 = _interopRequireDefault(_makeStateWithLocation); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function getComponentsForRoute(nextState, route, callback) { - if (route.component || route.components) { - callback(null, route.component || route.components); - return; - } - - var getComponent = route.getComponent || route.getComponents; - if (!getComponent) { - callback(); - return; - } - - var location = nextState.location; - - var nextStateWithLocation = (0, _makeStateWithLocation2.default)(nextState, location); - - getComponent.call(route, nextStateWithLocation, callback); -} - -/** - * Asynchronously fetches all components needed for the given router - * state and calls callback(error, components) when finished. - * - * Note: This operation may finish synchronously if no routes have an - * asynchronous getComponents method. - */ -function getComponents(nextState, callback) { - (0, _AsyncUtils.mapAsync)(nextState.routes, function (route, index, callback) { - getComponentsForRoute(nextState, route, callback); - }, callback); -} - -exports.default = getComponents; -module.exports = exports['default']; -},{"./AsyncUtils":45,"./makeStateWithLocation":75}],72:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _PatternUtils = require('./PatternUtils'); - -/** - * Extracts an object of params the given route cares about from - * the given params object. - */ -function getRouteParams(route, params) { - var routeParams = {}; - - if (!route.path) return routeParams; - - (0, _PatternUtils.getParamNames)(route.path).forEach(function (p) { - if (Object.prototype.hasOwnProperty.call(params, p)) { - routeParams[p] = params[p]; - } - }); - - return routeParams; -} - -exports.default = getRouteParams; -module.exports = exports['default']; -},{"./PatternUtils":53}],73:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _createHashHistory = require('history/lib/createHashHistory'); - -var _createHashHistory2 = _interopRequireDefault(_createHashHistory); - -var _createRouterHistory = require('./createRouterHistory'); - -var _createRouterHistory2 = _interopRequireDefault(_createRouterHistory); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = (0, _createRouterHistory2.default)(_createHashHistory2.default); -module.exports = exports['default']; -},{"./createRouterHistory":68,"history/lib/createHashHistory":15}],74:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -exports.default = isActive; - -var _PatternUtils = require('./PatternUtils'); - -function deepEqual(a, b) { - if (a == b) return true; - - if (a == null || b == null) return false; - - if (Array.isArray(a)) { - return Array.isArray(b) && a.length === b.length && a.every(function (item, index) { - return deepEqual(item, b[index]); - }); - } - - if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) === 'object') { - for (var p in a) { - if (!Object.prototype.hasOwnProperty.call(a, p)) { - continue; - } - - if (a[p] === undefined) { - if (b[p] !== undefined) { - return false; - } - } else if (!Object.prototype.hasOwnProperty.call(b, p)) { - return false; - } else if (!deepEqual(a[p], b[p])) { - return false; - } - } - - return true; - } - - return String(a) === String(b); -} - -/** - * Returns true if the current pathname matches the supplied one, net of - * leading and trailing slash normalization. This is sufficient for an - * indexOnly route match. - */ -function pathIsActive(pathname, currentPathname) { - // Normalize leading slash for consistency. Leading slash on pathname has - // already been normalized in isActive. See caveat there. - if (currentPathname.charAt(0) !== '/') { - currentPathname = '/' + currentPathname; - } - - // Normalize the end of both path names too. Maybe `/foo/` shouldn't show - // `/foo` as active, but in this case, we would already have failed the - // match. - if (pathname.charAt(pathname.length - 1) !== '/') { - pathname += '/'; - } - if (currentPathname.charAt(currentPathname.length - 1) !== '/') { - currentPathname += '/'; - } - - return currentPathname === pathname; -} - -/** - * Returns true if the given pathname matches the active routes and params. - */ -function routeIsActive(pathname, routes, params) { - var remainingPathname = pathname, - paramNames = [], - paramValues = []; - - // for...of would work here but it's probably slower post-transpilation. - for (var i = 0, len = routes.length; i < len; ++i) { - var route = routes[i]; - var pattern = route.path || ''; - - if (pattern.charAt(0) === '/') { - remainingPathname = pathname; - paramNames = []; - paramValues = []; - } - - if (remainingPathname !== null && pattern) { - var matched = (0, _PatternUtils.matchPattern)(pattern, remainingPathname); - if (matched) { - remainingPathname = matched.remainingPathname; - paramNames = [].concat(paramNames, matched.paramNames); - paramValues = [].concat(paramValues, matched.paramValues); - } else { - remainingPathname = null; - } - - if (remainingPathname === '') { - // We have an exact match on the route. Just check that all the params - // match. - // FIXME: This doesn't work on repeated params. - return paramNames.every(function (paramName, index) { - return String(paramValues[index]) === String(params[paramName]); - }); - } - } - } - - return false; -} - -/** - * Returns true if all key/value pairs in the given query are - * currently active. - */ -function queryIsActive(query, activeQuery) { - if (activeQuery == null) return query == null; - - if (query == null) return true; - - return deepEqual(query, activeQuery); -} - -/** - * Returns true if a <Link> to the given pathname/query combination is - * currently active. - */ -function isActive(_ref, indexOnly, currentLocation, routes, params) { - var pathname = _ref.pathname; - var query = _ref.query; - - if (currentLocation == null) return false; - - // TODO: This is a bit ugly. It keeps around support for treating pathnames - // without preceding slashes as absolute paths, but possibly also works - // around the same quirks with basenames as in matchRoutes. - if (pathname.charAt(0) !== '/') { - pathname = '/' + pathname; - } - - if (!pathIsActive(pathname, currentLocation.pathname)) { - // The path check is necessary and sufficient for indexOnly, but otherwise - // we still need to check the routes. - if (indexOnly || !routeIsActive(pathname, routes, params)) { - return false; - } - } - - return queryIsActive(query, currentLocation.query); -} -module.exports = exports['default']; -},{"./PatternUtils":53}],75:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.default = makeStateWithLocation; - -var _deprecateObjectProperties = require('./deprecateObjectProperties'); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function makeStateWithLocation(state, location) { - if (process.env.NODE_ENV !== 'production' && _deprecateObjectProperties.canUseMembrane) { - var stateWithLocation = _extends({}, state); - - // I don't use deprecateObjectProperties here because I want to keep the - // same code path between development and production, in that we just - // assign extra properties to the copy of the state object in both cases. - - var _loop = function _loop(prop) { - if (!Object.prototype.hasOwnProperty.call(location, prop)) { - return 'continue'; - } - - Object.defineProperty(stateWithLocation, prop, { - get: function get() { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, 'Accessing location properties directly from the first argument to `getComponent`, `getComponents`, `getChildRoutes`, and `getIndexRoute` is deprecated. That argument is now the router state (`nextState` or `partialNextState`) rather than the location. To access the location, use `nextState.location` or `partialNextState.location`.') : void 0; - return location[prop]; - } - }); - }; - - for (var prop in location) { - var _ret = _loop(prop); - - if (_ret === 'continue') continue; - } - - return stateWithLocation; - } - - return _extends({}, state, location); -} -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./deprecateObjectProperties":70,"./routerWarning":78,"_process":37}],76:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _invariant = require('invariant'); - -var _invariant2 = _interopRequireDefault(_invariant); - -var _createMemoryHistory = require('./createMemoryHistory'); - -var _createMemoryHistory2 = _interopRequireDefault(_createMemoryHistory); - -var _createTransitionManager = require('./createTransitionManager'); - -var _createTransitionManager2 = _interopRequireDefault(_createTransitionManager); - -var _RouteUtils = require('./RouteUtils'); - -var _RouterUtils = require('./RouterUtils'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -/** - * A high-level API to be used for server-side rendering. - * - * This function matches a location to a set of routes and calls - * callback(error, redirectLocation, renderProps) when finished. - * - * Note: You probably don't want to use this in a browser unless you're using - * server-side rendering with async routes. - */ -function match(_ref, callback) { - var history = _ref.history; - var routes = _ref.routes; - var location = _ref.location; - - var options = _objectWithoutProperties(_ref, ['history', 'routes', 'location']); - - !(history || location) ? process.env.NODE_ENV !== 'production' ? (0, _invariant2.default)(false, 'match needs a history or a location') : (0, _invariant2.default)(false) : void 0; - - history = history ? history : (0, _createMemoryHistory2.default)(options); - var transitionManager = (0, _createTransitionManager2.default)(history, (0, _RouteUtils.createRoutes)(routes)); - - var unlisten = void 0; - - if (location) { - // Allow match({ location: '/the/path', ... }) - location = history.createLocation(location); - } else { - // Pick up the location from the history via synchronous history.listen - // call if needed. - unlisten = history.listen(function (historyLocation) { - location = historyLocation; - }); - } - - var router = (0, _RouterUtils.createRouterObject)(history, transitionManager); - history = (0, _RouterUtils.createRoutingHistory)(history, transitionManager); - - transitionManager.match(location, function (error, redirectLocation, nextState) { - callback(error, redirectLocation, nextState && _extends({}, nextState, { - history: history, - router: router, - matchContext: { history: history, transitionManager: transitionManager, router: router } - })); - - // Defer removing the listener to here to prevent DOM histories from having - // to unwind DOM event listeners unnecessarily, in case callback renders a - // <Router> and attaches another history listener. - if (unlisten) { - unlisten(); - } - }); -} - -exports.default = match; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./RouteUtils":58,"./RouterUtils":61,"./createMemoryHistory":67,"./createTransitionManager":69,"_process":37,"invariant":25}],77:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -exports.default = matchRoutes; - -var _AsyncUtils = require('./AsyncUtils'); - -var _makeStateWithLocation = require('./makeStateWithLocation'); - -var _makeStateWithLocation2 = _interopRequireDefault(_makeStateWithLocation); - -var _PatternUtils = require('./PatternUtils'); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -var _RouteUtils = require('./RouteUtils'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function getChildRoutes(route, location, paramNames, paramValues, callback) { - if (route.childRoutes) { - return [null, route.childRoutes]; - } - if (!route.getChildRoutes) { - return []; - } - - var sync = true, - result = void 0; - - var partialNextState = { - location: location, - params: createParams(paramNames, paramValues) - }; - - var partialNextStateWithLocation = (0, _makeStateWithLocation2.default)(partialNextState, location); - - route.getChildRoutes(partialNextStateWithLocation, function (error, childRoutes) { - childRoutes = !error && (0, _RouteUtils.createRoutes)(childRoutes); - if (sync) { - result = [error, childRoutes]; - return; - } - - callback(error, childRoutes); - }); - - sync = false; - return result; // Might be undefined. -} - -function getIndexRoute(route, location, paramNames, paramValues, callback) { - if (route.indexRoute) { - callback(null, route.indexRoute); - } else if (route.getIndexRoute) { - var partialNextState = { - location: location, - params: createParams(paramNames, paramValues) - }; - - var partialNextStateWithLocation = (0, _makeStateWithLocation2.default)(partialNextState, location); - - route.getIndexRoute(partialNextStateWithLocation, function (error, indexRoute) { - callback(error, !error && (0, _RouteUtils.createRoutes)(indexRoute)[0]); - }); - } else if (route.childRoutes) { - (function () { - var pathless = route.childRoutes.filter(function (childRoute) { - return !childRoute.path; - }); - - (0, _AsyncUtils.loopAsync)(pathless.length, function (index, next, done) { - getIndexRoute(pathless[index], location, paramNames, paramValues, function (error, indexRoute) { - if (error || indexRoute) { - var routes = [pathless[index]].concat(Array.isArray(indexRoute) ? indexRoute : [indexRoute]); - done(error, routes); - } else { - next(); - } - }); - }, function (err, routes) { - callback(null, routes); - }); - })(); - } else { - callback(); - } -} - -function assignParams(params, paramNames, paramValues) { - return paramNames.reduce(function (params, paramName, index) { - var paramValue = paramValues && paramValues[index]; - - if (Array.isArray(params[paramName])) { - params[paramName].push(paramValue); - } else if (paramName in params) { - params[paramName] = [params[paramName], paramValue]; - } else { - params[paramName] = paramValue; - } - - return params; - }, params); -} - -function createParams(paramNames, paramValues) { - return assignParams({}, paramNames, paramValues); -} - -function matchRouteDeep(route, location, remainingPathname, paramNames, paramValues, callback) { - var pattern = route.path || ''; - - if (pattern.charAt(0) === '/') { - remainingPathname = location.pathname; - paramNames = []; - paramValues = []; - } - - // Only try to match the path if the route actually has a pattern, and if - // we're not just searching for potential nested absolute paths. - if (remainingPathname !== null && pattern) { - try { - var matched = (0, _PatternUtils.matchPattern)(pattern, remainingPathname); - if (matched) { - remainingPathname = matched.remainingPathname; - paramNames = [].concat(paramNames, matched.paramNames); - paramValues = [].concat(paramValues, matched.paramValues); - } else { - remainingPathname = null; - } - } catch (error) { - callback(error); - } - - // By assumption, pattern is non-empty here, which is the prerequisite for - // actually terminating a match. - if (remainingPathname === '') { - var _ret2 = function () { - var match = { - routes: [route], - params: createParams(paramNames, paramValues) - }; - - getIndexRoute(route, location, paramNames, paramValues, function (error, indexRoute) { - if (error) { - callback(error); - } else { - if (Array.isArray(indexRoute)) { - var _match$routes; - - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(indexRoute.every(function (route) { - return !route.path; - }), 'Index routes should not have paths') : void 0; - (_match$routes = match.routes).push.apply(_match$routes, indexRoute); - } else if (indexRoute) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(!indexRoute.path, 'Index routes should not have paths') : void 0; - match.routes.push(indexRoute); - } - - callback(null, match); - } - }); - - return { - v: void 0 - }; - }(); - - if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; - } - } - - if (remainingPathname != null || route.childRoutes) { - // Either a) this route matched at least some of the path or b) - // we don't have to load this route's children asynchronously. In - // either case continue checking for matches in the subtree. - var onChildRoutes = function onChildRoutes(error, childRoutes) { - if (error) { - callback(error); - } else if (childRoutes) { - // Check the child routes to see if any of them match. - matchRoutes(childRoutes, location, function (error, match) { - if (error) { - callback(error); - } else if (match) { - // A child route matched! Augment the match and pass it up the stack. - match.routes.unshift(route); - callback(null, match); - } else { - callback(); - } - }, remainingPathname, paramNames, paramValues); - } else { - callback(); - } - }; - - var result = getChildRoutes(route, location, paramNames, paramValues, onChildRoutes); - if (result) { - onChildRoutes.apply(undefined, result); - } - } else { - callback(); - } -} - -/** - * Asynchronously matches the given location to a set of routes and calls - * callback(error, state) when finished. The state object will have the - * following properties: - * - * - routes An array of routes that matched, in hierarchical order - * - params An object of URL parameters - * - * Note: This operation may finish synchronously if no routes have an - * asynchronous getChildRoutes method. - */ -function matchRoutes(routes, location, callback, remainingPathname) { - var paramNames = arguments.length <= 4 || arguments[4] === undefined ? [] : arguments[4]; - var paramValues = arguments.length <= 5 || arguments[5] === undefined ? [] : arguments[5]; - - if (remainingPathname === undefined) { - // TODO: This is a little bit ugly, but it works around a quirk in history - // that strips the leading slash from pathnames when using basenames with - // trailing slashes. - if (location.pathname.charAt(0) !== '/') { - location = _extends({}, location, { - pathname: '/' + location.pathname - }); - } - remainingPathname = location.pathname; - } - - (0, _AsyncUtils.loopAsync)(routes.length, function (index, next, done) { - matchRouteDeep(routes[index], location, remainingPathname, paramNames, paramValues, function (error, match) { - if (error || match) { - done(error, match); - } else { - next(); - } - }); - }, callback); -} -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./AsyncUtils":45,"./PatternUtils":53,"./RouteUtils":58,"./makeStateWithLocation":75,"./routerWarning":78,"_process":37}],78:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.default = routerWarning; -exports._resetWarned = _resetWarned; - -var _warning = require('warning'); - -var _warning2 = _interopRequireDefault(_warning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var warned = {}; - -function routerWarning(falseToWarn, message) { - // Only issue deprecation warnings once. - if (message.indexOf('deprecated') !== -1) { - if (warned[message]) { - return; - } - - warned[message] = true; - } - - message = '[react-router] ' + message; - - for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { - args[_key - 2] = arguments[_key]; - } - - _warning2.default.apply(undefined, [falseToWarn, message].concat(args)); -} - -function _resetWarned() { - warned = {}; -} -},{"warning":261}],79:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.default = useRouterHistory; - -var _useQueries = require('history/lib/useQueries'); - -var _useQueries2 = _interopRequireDefault(_useQueries); - -var _useBasename = require('history/lib/useBasename'); - -var _useBasename2 = _interopRequireDefault(_useBasename); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function useRouterHistory(createHistory) { - return function (options) { - var history = (0, _useQueries2.default)((0, _useBasename2.default)(createHistory))(options); - history.__v2_compatible__ = true; - return history; - }; -} -module.exports = exports['default']; -},{"history/lib/useBasename":21,"history/lib/useQueries":22}],80:[function(require,module,exports){ -(function (process){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _useQueries = require('history/lib/useQueries'); - -var _useQueries2 = _interopRequireDefault(_useQueries); - -var _createTransitionManager = require('./createTransitionManager'); - -var _createTransitionManager2 = _interopRequireDefault(_createTransitionManager); - -var _routerWarning = require('./routerWarning'); - -var _routerWarning2 = _interopRequireDefault(_routerWarning); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -/** - * Returns a new createHistory function that may be used to create - * history objects that know about routing. - * - * Enhances history objects with the following methods: - * - * - listen((error, nextState) => {}) - * - listenBeforeLeavingRoute(route, (nextLocation) => {}) - * - match(location, (error, redirectLocation, nextState) => {}) - * - isActive(pathname, query, indexOnly=false) - */ -function useRoutes(createHistory) { - process.env.NODE_ENV !== 'production' ? (0, _routerWarning2.default)(false, '`useRoutes` is deprecated. Please use `createTransitionManager` instead.') : void 0; - - return function () { - var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - var routes = _ref.routes; - - var options = _objectWithoutProperties(_ref, ['routes']); - - var history = (0, _useQueries2.default)(createHistory)(options); - var transitionManager = (0, _createTransitionManager2.default)(history, routes); - return _extends({}, history, transitionManager); - }; -} - -exports.default = useRoutes; -module.exports = exports['default']; -}).call(this,require('_process')) - -},{"./createTransitionManager":69,"./routerWarning":78,"_process":37,"history/lib/useQueries":22}],81:[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.default = withRouter; - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _hoistNonReactStatics = require('hoist-non-react-statics'); - -var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics); - -var _PropTypes = require('./PropTypes'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function getDisplayName(WrappedComponent) { - return WrappedComponent.displayName || WrappedComponent.name || 'Component'; -} - -function withRouter(WrappedComponent) { - var WithRouter = _react2.default.createClass({ - displayName: 'WithRouter', - - contextTypes: { router: _PropTypes.routerShape }, - render: function render() { - return _react2.default.createElement(WrappedComponent, _extends({}, this.props, { router: this.context.router })); - } - }); - - WithRouter.displayName = 'withRouter(' + getDisplayName(WrappedComponent) + ')'; - WithRouter.WrappedComponent = WrappedComponent; - - return (0, _hoistNonReactStatics2.default)(WithRouter, WrappedComponent); -} -module.exports = exports['default']; -},{"./PropTypes":54,"hoist-non-react-statics":24,"react":"react"}],82:[function(require,module,exports){ +},{"redux":"redux"}],48:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -16601,7 +13589,7 @@ var AutoFocusUtils = { }; module.exports = AutoFocusUtils; -},{"./ReactDOMComponentTree":123,"fbjs/lib/focusNode":235}],83:[function(require,module,exports){ +},{"./ReactDOMComponentTree":89,"fbjs/lib/focusNode":201}],49:[function(require,module,exports){ /** * Copyright 2013-present Facebook, Inc. * All rights reserved. @@ -16990,7 +13978,7 @@ var BeforeInputEventPlugin = { }; module.exports = BeforeInputEventPlugin; -},{"./EventConstants":97,"./EventPropagators":101,"./FallbackCompositionState":102,"./SyntheticCompositionEvent":182,"./SyntheticInputEvent":186,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/keyOf":245}],84:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPropagators":67,"./FallbackCompositionState":68,"./SyntheticCompositionEvent":148,"./SyntheticInputEvent":152,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/keyOf":211}],50:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -17139,7 +14127,7 @@ var CSSProperty = { }; module.exports = CSSProperty; -},{}],85:[function(require,module,exports){ +},{}],51:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -17348,7 +14336,7 @@ var CSSPropertyOperations = { module.exports = CSSPropertyOperations; }).call(this,require('_process')) -},{"./CSSProperty":84,"./ReactInstrumentation":155,"./dangerousStyleValue":200,"_process":37,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/camelizeStyleName":229,"fbjs/lib/hyphenateStyleName":240,"fbjs/lib/memoizeStringOnly":247,"fbjs/lib/warning":251}],86:[function(require,module,exports){ +},{"./CSSProperty":50,"./ReactInstrumentation":121,"./dangerousStyleValue":166,"_process":40,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/camelizeStyleName":195,"fbjs/lib/hyphenateStyleName":206,"fbjs/lib/memoizeStringOnly":213,"fbjs/lib/warning":217}],52:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -17458,7 +14446,7 @@ PooledClass.addPoolingTo(CallbackQueue); module.exports = CallbackQueue; }).call(this,require('_process')) -},{"./PooledClass":106,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"object-assign":36}],87:[function(require,module,exports){ +},{"./PooledClass":72,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"object-assign":39}],53:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -17784,7 +14772,7 @@ var ChangeEventPlugin = { }; module.exports = ChangeEventPlugin; -},{"./EventConstants":97,"./EventPluginHub":98,"./EventPropagators":101,"./ReactDOMComponentTree":123,"./ReactUpdates":175,"./SyntheticEvent":184,"./getEventTarget":208,"./isEventSupported":215,"./isTextInputElement":216,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/keyOf":245}],88:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPluginHub":64,"./EventPropagators":67,"./ReactDOMComponentTree":89,"./ReactUpdates":141,"./SyntheticEvent":150,"./getEventTarget":174,"./isEventSupported":181,"./isTextInputElement":182,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/keyOf":211}],54:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -17982,7 +14970,7 @@ var DOMChildrenOperations = { module.exports = DOMChildrenOperations; }).call(this,require('_process')) -},{"./DOMLazyTree":89,"./Danger":93,"./ReactDOMComponentTree":123,"./ReactInstrumentation":155,"./ReactMultiChildUpdateTypes":160,"./createMicrosoftUnsafeLocalFunction":199,"./setInnerHTML":221,"./setTextContent":222,"_process":37}],89:[function(require,module,exports){ +},{"./DOMLazyTree":55,"./Danger":59,"./ReactDOMComponentTree":89,"./ReactInstrumentation":121,"./ReactMultiChildUpdateTypes":126,"./createMicrosoftUnsafeLocalFunction":165,"./setInnerHTML":187,"./setTextContent":188,"_process":40}],55:[function(require,module,exports){ /** * Copyright 2015-present, Facebook, Inc. * All rights reserved. @@ -18101,7 +15089,7 @@ DOMLazyTree.queueHTML = queueHTML; DOMLazyTree.queueText = queueText; module.exports = DOMLazyTree; -},{"./DOMNamespaces":90,"./createMicrosoftUnsafeLocalFunction":199,"./setInnerHTML":221,"./setTextContent":222}],90:[function(require,module,exports){ +},{"./DOMNamespaces":56,"./createMicrosoftUnsafeLocalFunction":165,"./setInnerHTML":187,"./setTextContent":188}],56:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -18122,7 +15110,7 @@ var DOMNamespaces = { }; module.exports = DOMNamespaces; -},{}],91:[function(require,module,exports){ +},{}],57:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -18332,7 +15320,7 @@ var DOMProperty = { module.exports = DOMProperty; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],92:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],58:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -18564,7 +15552,7 @@ var DOMPropertyOperations = { module.exports = DOMPropertyOperations; }).call(this,require('_process')) -},{"./DOMProperty":91,"./ReactDOMComponentTree":123,"./ReactDOMInstrumentation":131,"./ReactInstrumentation":155,"./quoteAttributeValueForBrowser":218,"_process":37,"fbjs/lib/warning":251}],93:[function(require,module,exports){ +},{"./DOMProperty":57,"./ReactDOMComponentTree":89,"./ReactDOMInstrumentation":97,"./ReactInstrumentation":121,"./quoteAttributeValueForBrowser":184,"_process":40,"fbjs/lib/warning":217}],59:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -18616,7 +15604,7 @@ var Danger = { module.exports = Danger; }).call(this,require('_process')) -},{"./DOMLazyTree":89,"./reactProdInvariant":219,"_process":37,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/createNodesFromMarkup":232,"fbjs/lib/emptyFunction":233,"fbjs/lib/invariant":241}],94:[function(require,module,exports){ +},{"./DOMLazyTree":55,"./reactProdInvariant":185,"_process":40,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/createNodesFromMarkup":198,"fbjs/lib/emptyFunction":199,"fbjs/lib/invariant":207}],60:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -18644,7 +15632,7 @@ var keyOf = require('fbjs/lib/keyOf'); var DefaultEventPluginOrder = [keyOf({ ResponderEventPlugin: null }), keyOf({ SimpleEventPlugin: null }), keyOf({ TapEventPlugin: null }), keyOf({ EnterLeaveEventPlugin: null }), keyOf({ ChangeEventPlugin: null }), keyOf({ SelectEventPlugin: null }), keyOf({ BeforeInputEventPlugin: null })]; module.exports = DefaultEventPluginOrder; -},{"fbjs/lib/keyOf":245}],95:[function(require,module,exports){ +},{"fbjs/lib/keyOf":211}],61:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -18695,7 +15683,7 @@ var DisabledInputUtils = { }; module.exports = DisabledInputUtils; -},{}],96:[function(require,module,exports){ +},{}],62:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -18801,7 +15789,7 @@ var EnterLeaveEventPlugin = { }; module.exports = EnterLeaveEventPlugin; -},{"./EventConstants":97,"./EventPropagators":101,"./ReactDOMComponentTree":123,"./SyntheticMouseEvent":188,"fbjs/lib/keyOf":245}],97:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPropagators":67,"./ReactDOMComponentTree":89,"./SyntheticMouseEvent":154,"fbjs/lib/keyOf":211}],63:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -18899,7 +15887,7 @@ var EventConstants = { }; module.exports = EventConstants; -},{"fbjs/lib/keyMirror":244}],98:[function(require,module,exports){ +},{"fbjs/lib/keyMirror":210}],64:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -19152,7 +16140,7 @@ var EventPluginHub = { module.exports = EventPluginHub; }).call(this,require('_process')) -},{"./EventPluginRegistry":99,"./EventPluginUtils":100,"./ReactErrorUtils":146,"./accumulateInto":195,"./forEachAccumulated":204,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],99:[function(require,module,exports){ +},{"./EventPluginRegistry":65,"./EventPluginUtils":66,"./ReactErrorUtils":112,"./accumulateInto":161,"./forEachAccumulated":170,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],65:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -19403,7 +16391,7 @@ var EventPluginRegistry = { module.exports = EventPluginRegistry; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],100:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],66:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -19636,7 +16624,7 @@ var EventPluginUtils = { module.exports = EventPluginUtils; }).call(this,require('_process')) -},{"./EventConstants":97,"./ReactErrorUtils":146,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],101:[function(require,module,exports){ +},{"./EventConstants":63,"./ReactErrorUtils":112,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],67:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -19777,7 +16765,7 @@ var EventPropagators = { module.exports = EventPropagators; }).call(this,require('_process')) -},{"./EventConstants":97,"./EventPluginHub":98,"./EventPluginUtils":100,"./accumulateInto":195,"./forEachAccumulated":204,"_process":37,"fbjs/lib/warning":251}],102:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPluginHub":64,"./EventPluginUtils":66,"./accumulateInto":161,"./forEachAccumulated":170,"_process":40,"fbjs/lib/warning":217}],68:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -19873,7 +16861,7 @@ _assign(FallbackCompositionState.prototype, { PooledClass.addPoolingTo(FallbackCompositionState); module.exports = FallbackCompositionState; -},{"./PooledClass":106,"./getTextContentAccessor":212,"object-assign":36}],103:[function(require,module,exports){ +},{"./PooledClass":72,"./getTextContentAccessor":178,"object-assign":39}],69:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -20083,7 +17071,7 @@ var HTMLDOMPropertyConfig = { }; module.exports = HTMLDOMPropertyConfig; -},{"./DOMProperty":91}],104:[function(require,module,exports){ +},{"./DOMProperty":57}],70:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -20143,7 +17131,7 @@ var KeyEscapeUtils = { }; module.exports = KeyEscapeUtils; -},{}],105:[function(require,module,exports){ +},{}],71:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -20283,7 +17271,7 @@ var LinkedValueUtils = { module.exports = LinkedValueUtils; }).call(this,require('_process')) -},{"./ReactPropTypeLocations":165,"./ReactPropTypes":166,"./ReactPropTypesSecret":167,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],106:[function(require,module,exports){ +},{"./ReactPropTypeLocations":131,"./ReactPropTypes":132,"./ReactPropTypesSecret":133,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],72:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -20408,7 +17396,7 @@ var PooledClass = { module.exports = PooledClass; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],107:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],73:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -20501,7 +17489,7 @@ var React = { module.exports = React; }).call(this,require('_process')) -},{"./ReactChildren":110,"./ReactClass":112,"./ReactComponent":113,"./ReactDOMFactories":127,"./ReactElement":143,"./ReactElementValidator":144,"./ReactPropTypes":166,"./ReactPureComponent":168,"./ReactVersion":176,"./onlyChild":217,"_process":37,"fbjs/lib/warning":251,"object-assign":36}],108:[function(require,module,exports){ +},{"./ReactChildren":76,"./ReactClass":78,"./ReactComponent":79,"./ReactDOMFactories":93,"./ReactElement":109,"./ReactElementValidator":110,"./ReactPropTypes":132,"./ReactPureComponent":134,"./ReactVersion":142,"./onlyChild":183,"_process":40,"fbjs/lib/warning":217,"object-assign":39}],74:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -20819,7 +17807,7 @@ var ReactBrowserEventEmitter = _assign({}, ReactEventEmitterMixin, { }); module.exports = ReactBrowserEventEmitter; -},{"./EventConstants":97,"./EventPluginRegistry":99,"./ReactEventEmitterMixin":147,"./ViewportMetrics":194,"./getVendorPrefixedEventName":213,"./isEventSupported":215,"object-assign":36}],109:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPluginRegistry":65,"./ReactEventEmitterMixin":113,"./ViewportMetrics":160,"./getVendorPrefixedEventName":179,"./isEventSupported":181,"object-assign":39}],75:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -20974,7 +17962,7 @@ var ReactChildReconciler = { module.exports = ReactChildReconciler; }).call(this,require('_process')) -},{"./KeyEscapeUtils":104,"./ReactComponentTreeDevtool":116,"./ReactReconciler":170,"./instantiateReactComponent":214,"./shouldUpdateReactComponent":223,"./traverseAllChildren":224,"_process":37,"fbjs/lib/warning":251}],110:[function(require,module,exports){ +},{"./KeyEscapeUtils":70,"./ReactComponentTreeDevtool":82,"./ReactReconciler":136,"./instantiateReactComponent":180,"./shouldUpdateReactComponent":189,"./traverseAllChildren":190,"_process":40,"fbjs/lib/warning":217}],76:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -21166,7 +18154,7 @@ var ReactChildren = { }; module.exports = ReactChildren; -},{"./PooledClass":106,"./ReactElement":143,"./traverseAllChildren":224,"fbjs/lib/emptyFunction":233}],111:[function(require,module,exports){ +},{"./PooledClass":72,"./ReactElement":109,"./traverseAllChildren":190,"fbjs/lib/emptyFunction":199}],77:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -21232,7 +18220,7 @@ var ReactDOMUnknownPropertyDevtool = { module.exports = ReactDOMUnknownPropertyDevtool; }).call(this,require('_process')) -},{"./ReactComponentTreeDevtool":116,"_process":37,"fbjs/lib/warning":251}],112:[function(require,module,exports){ +},{"./ReactComponentTreeDevtool":82,"_process":40,"fbjs/lib/warning":217}],78:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -21968,7 +18956,7 @@ var ReactClass = { module.exports = ReactClass; }).call(this,require('_process')) -},{"./ReactComponent":113,"./ReactElement":143,"./ReactNoopUpdateQueue":162,"./ReactPropTypeLocationNames":164,"./ReactPropTypeLocations":165,"./reactProdInvariant":219,"_process":37,"fbjs/lib/emptyObject":234,"fbjs/lib/invariant":241,"fbjs/lib/keyMirror":244,"fbjs/lib/keyOf":245,"fbjs/lib/warning":251,"object-assign":36}],113:[function(require,module,exports){ +},{"./ReactComponent":79,"./ReactElement":109,"./ReactNoopUpdateQueue":128,"./ReactPropTypeLocationNames":130,"./ReactPropTypeLocations":131,"./reactProdInvariant":185,"_process":40,"fbjs/lib/emptyObject":200,"fbjs/lib/invariant":207,"fbjs/lib/keyMirror":210,"fbjs/lib/keyOf":211,"fbjs/lib/warning":217,"object-assign":39}],79:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -22090,7 +19078,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = ReactComponent; }).call(this,require('_process')) -},{"./ReactNoopUpdateQueue":162,"./canDefineProperty":197,"./reactProdInvariant":219,"_process":37,"fbjs/lib/emptyObject":234,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],114:[function(require,module,exports){ +},{"./ReactNoopUpdateQueue":128,"./canDefineProperty":163,"./reactProdInvariant":185,"_process":40,"fbjs/lib/emptyObject":200,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],80:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -22130,7 +19118,7 @@ var ReactComponentBrowserEnvironment = { }; module.exports = ReactComponentBrowserEnvironment; -},{"./DOMChildrenOperations":88,"./ReactDOMIDOperations":129}],115:[function(require,module,exports){ +},{"./DOMChildrenOperations":54,"./ReactDOMIDOperations":95}],81:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -22187,7 +19175,7 @@ var ReactComponentEnvironment = { module.exports = ReactComponentEnvironment; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],116:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],82:[function(require,module,exports){ (function (process){ /** * Copyright 2016-present, Facebook, Inc. @@ -22409,7 +19397,7 @@ var ReactComponentTreeDevtool = { module.exports = ReactComponentTreeDevtool; }).call(this,require('_process')) -},{"./ReactCurrentOwner":118,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],117:[function(require,module,exports){ +},{"./ReactCurrentOwner":84,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],83:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -23357,7 +20345,7 @@ var ReactCompositeComponent = { module.exports = ReactCompositeComponent; }).call(this,require('_process')) -},{"./ReactComponentEnvironment":115,"./ReactCurrentOwner":118,"./ReactElement":143,"./ReactErrorUtils":146,"./ReactInstanceMap":154,"./ReactInstrumentation":155,"./ReactNodeTypes":161,"./ReactPropTypeLocations":165,"./ReactReconciler":170,"./checkReactTypeSpec":198,"./reactProdInvariant":219,"./shouldUpdateReactComponent":223,"_process":37,"fbjs/lib/emptyObject":234,"fbjs/lib/invariant":241,"fbjs/lib/shallowEqual":250,"fbjs/lib/warning":251,"object-assign":36}],118:[function(require,module,exports){ +},{"./ReactComponentEnvironment":81,"./ReactCurrentOwner":84,"./ReactElement":109,"./ReactErrorUtils":112,"./ReactInstanceMap":120,"./ReactInstrumentation":121,"./ReactNodeTypes":127,"./ReactPropTypeLocations":131,"./ReactReconciler":136,"./checkReactTypeSpec":164,"./reactProdInvariant":185,"./shouldUpdateReactComponent":189,"_process":40,"fbjs/lib/emptyObject":200,"fbjs/lib/invariant":207,"fbjs/lib/shallowEqual":216,"fbjs/lib/warning":217,"object-assign":39}],84:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -23389,7 +20377,7 @@ var ReactCurrentOwner = { }; module.exports = ReactCurrentOwner; -},{}],119:[function(require,module,exports){ +},{}],85:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -23494,7 +20482,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = ReactDOM; }).call(this,require('_process')) -},{"./ReactDOMComponentTree":123,"./ReactDefaultInjection":142,"./ReactMount":158,"./ReactReconciler":170,"./ReactUpdates":175,"./ReactVersion":176,"./findDOMNode":202,"./getHostComponentFromComposite":209,"./renderSubtreeIntoContainer":220,"_process":37,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/warning":251}],120:[function(require,module,exports){ +},{"./ReactDOMComponentTree":89,"./ReactDefaultInjection":108,"./ReactMount":124,"./ReactReconciler":136,"./ReactUpdates":141,"./ReactVersion":142,"./findDOMNode":168,"./getHostComponentFromComposite":175,"./renderSubtreeIntoContainer":186,"_process":40,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/warning":217}],86:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -23519,7 +20507,7 @@ var ReactDOMButton = { }; module.exports = ReactDOMButton; -},{"./DisabledInputUtils":95}],121:[function(require,module,exports){ +},{"./DisabledInputUtils":61}],87:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -24548,7 +21536,7 @@ _assign(ReactDOMComponent.prototype, ReactDOMComponent.Mixin, ReactMultiChild.Mi module.exports = ReactDOMComponent; }).call(this,require('_process')) -},{"./AutoFocusUtils":82,"./CSSPropertyOperations":85,"./DOMLazyTree":89,"./DOMNamespaces":90,"./DOMProperty":91,"./DOMPropertyOperations":92,"./EventConstants":97,"./EventPluginHub":98,"./EventPluginRegistry":99,"./ReactBrowserEventEmitter":108,"./ReactComponentBrowserEnvironment":114,"./ReactDOMButton":120,"./ReactDOMComponentFlags":122,"./ReactDOMComponentTree":123,"./ReactDOMInput":130,"./ReactDOMOption":133,"./ReactDOMSelect":134,"./ReactDOMTextarea":137,"./ReactInstrumentation":155,"./ReactMultiChild":159,"./ReactServerRenderingTransaction":172,"./escapeTextContentForBrowser":201,"./isEventSupported":215,"./reactProdInvariant":219,"./validateDOMNesting":225,"_process":37,"fbjs/lib/emptyFunction":233,"fbjs/lib/invariant":241,"fbjs/lib/keyOf":245,"fbjs/lib/shallowEqual":250,"fbjs/lib/warning":251,"object-assign":36}],122:[function(require,module,exports){ +},{"./AutoFocusUtils":48,"./CSSPropertyOperations":51,"./DOMLazyTree":55,"./DOMNamespaces":56,"./DOMProperty":57,"./DOMPropertyOperations":58,"./EventConstants":63,"./EventPluginHub":64,"./EventPluginRegistry":65,"./ReactBrowserEventEmitter":74,"./ReactComponentBrowserEnvironment":80,"./ReactDOMButton":86,"./ReactDOMComponentFlags":88,"./ReactDOMComponentTree":89,"./ReactDOMInput":96,"./ReactDOMOption":99,"./ReactDOMSelect":100,"./ReactDOMTextarea":103,"./ReactInstrumentation":121,"./ReactMultiChild":125,"./ReactServerRenderingTransaction":138,"./escapeTextContentForBrowser":167,"./isEventSupported":181,"./reactProdInvariant":185,"./validateDOMNesting":191,"_process":40,"fbjs/lib/emptyFunction":199,"fbjs/lib/invariant":207,"fbjs/lib/keyOf":211,"fbjs/lib/shallowEqual":216,"fbjs/lib/warning":217,"object-assign":39}],88:[function(require,module,exports){ /** * Copyright 2015-present, Facebook, Inc. * All rights reserved. @@ -24567,7 +21555,7 @@ var ReactDOMComponentFlags = { }; module.exports = ReactDOMComponentFlags; -},{}],123:[function(require,module,exports){ +},{}],89:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -24759,7 +21747,7 @@ var ReactDOMComponentTree = { module.exports = ReactDOMComponentTree; }).call(this,require('_process')) -},{"./DOMProperty":91,"./ReactDOMComponentFlags":122,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],124:[function(require,module,exports){ +},{"./DOMProperty":57,"./ReactDOMComponentFlags":88,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],90:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -24796,7 +21784,7 @@ function ReactDOMContainerInfo(topLevelWrapper, node) { module.exports = ReactDOMContainerInfo; }).call(this,require('_process')) -},{"./validateDOMNesting":225,"_process":37}],125:[function(require,module,exports){ +},{"./validateDOMNesting":191,"_process":40}],91:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -24867,7 +21855,7 @@ ReactDOMDebugTool.addDevtool(ReactDOMNullInputValuePropDevtool); module.exports = ReactDOMDebugTool; }).call(this,require('_process')) -},{"./ReactDOMNullInputValuePropDevtool":132,"./ReactDOMUnknownPropertyDevtool":139,"./ReactDebugTool":140,"_process":37,"fbjs/lib/warning":251}],126:[function(require,module,exports){ +},{"./ReactDOMNullInputValuePropDevtool":98,"./ReactDOMUnknownPropertyDevtool":105,"./ReactDebugTool":106,"_process":40,"fbjs/lib/warning":217}],92:[function(require,module,exports){ /** * Copyright 2014-present, Facebook, Inc. * All rights reserved. @@ -24928,7 +21916,7 @@ _assign(ReactDOMEmptyComponent.prototype, { }); module.exports = ReactDOMEmptyComponent; -},{"./DOMLazyTree":89,"./ReactDOMComponentTree":123,"object-assign":36}],127:[function(require,module,exports){ +},{"./DOMLazyTree":55,"./ReactDOMComponentTree":89,"object-assign":39}],93:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25108,7 +22096,7 @@ var ReactDOMFactories = mapObject({ module.exports = ReactDOMFactories; }).call(this,require('_process')) -},{"./ReactElement":143,"./ReactElementValidator":144,"_process":37,"fbjs/lib/mapObject":246}],128:[function(require,module,exports){ +},{"./ReactElement":109,"./ReactElementValidator":110,"_process":40,"fbjs/lib/mapObject":212}],94:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -25127,7 +22115,7 @@ var ReactDOMFeatureFlags = { }; module.exports = ReactDOMFeatureFlags; -},{}],129:[function(require,module,exports){ +},{}],95:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -25162,7 +22150,7 @@ var ReactDOMIDOperations = { }; module.exports = ReactDOMIDOperations; -},{"./DOMChildrenOperations":88,"./ReactDOMComponentTree":123}],130:[function(require,module,exports){ +},{"./DOMChildrenOperations":54,"./ReactDOMComponentTree":89}],96:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25413,7 +22401,7 @@ function _handleChange(event) { module.exports = ReactDOMInput; }).call(this,require('_process')) -},{"./DOMPropertyOperations":92,"./DisabledInputUtils":95,"./LinkedValueUtils":105,"./ReactDOMComponentTree":123,"./ReactUpdates":175,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251,"object-assign":36}],131:[function(require,module,exports){ +},{"./DOMPropertyOperations":58,"./DisabledInputUtils":61,"./LinkedValueUtils":71,"./ReactDOMComponentTree":89,"./ReactUpdates":141,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217,"object-assign":39}],97:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25438,7 +22426,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = { debugTool: debugTool }; }).call(this,require('_process')) -},{"./ReactDOMDebugTool":125,"_process":37}],132:[function(require,module,exports){ +},{"./ReactDOMDebugTool":91,"_process":40}],98:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25485,7 +22473,7 @@ var ReactDOMUnknownPropertyDevtool = { module.exports = ReactDOMUnknownPropertyDevtool; }).call(this,require('_process')) -},{"./ReactComponentTreeDevtool":116,"_process":37,"fbjs/lib/warning":251}],133:[function(require,module,exports){ +},{"./ReactComponentTreeDevtool":82,"_process":40,"fbjs/lib/warning":217}],99:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25612,7 +22600,7 @@ var ReactDOMOption = { module.exports = ReactDOMOption; }).call(this,require('_process')) -},{"./ReactChildren":110,"./ReactDOMComponentTree":123,"./ReactDOMSelect":134,"_process":37,"fbjs/lib/warning":251,"object-assign":36}],134:[function(require,module,exports){ +},{"./ReactChildren":76,"./ReactDOMComponentTree":89,"./ReactDOMSelect":100,"_process":40,"fbjs/lib/warning":217,"object-assign":39}],100:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -25816,7 +22804,7 @@ function _handleChange(event) { module.exports = ReactDOMSelect; }).call(this,require('_process')) -},{"./DisabledInputUtils":95,"./LinkedValueUtils":105,"./ReactDOMComponentTree":123,"./ReactUpdates":175,"_process":37,"fbjs/lib/warning":251,"object-assign":36}],135:[function(require,module,exports){ +},{"./DisabledInputUtils":61,"./LinkedValueUtils":71,"./ReactDOMComponentTree":89,"./ReactUpdates":141,"_process":40,"fbjs/lib/warning":217,"object-assign":39}],101:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -26029,7 +23017,7 @@ var ReactDOMSelection = { }; module.exports = ReactDOMSelection; -},{"./getNodeForCharacterOffset":211,"./getTextContentAccessor":212,"fbjs/lib/ExecutionEnvironment":227}],136:[function(require,module,exports){ +},{"./getNodeForCharacterOffset":177,"./getTextContentAccessor":178,"fbjs/lib/ExecutionEnvironment":193}],102:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -26204,7 +23192,7 @@ _assign(ReactDOMTextComponent.prototype, { module.exports = ReactDOMTextComponent; }).call(this,require('_process')) -},{"./DOMChildrenOperations":88,"./DOMLazyTree":89,"./ReactDOMComponentTree":123,"./ReactInstrumentation":155,"./escapeTextContentForBrowser":201,"./reactProdInvariant":219,"./validateDOMNesting":225,"_process":37,"fbjs/lib/invariant":241,"object-assign":36}],137:[function(require,module,exports){ +},{"./DOMChildrenOperations":54,"./DOMLazyTree":55,"./ReactDOMComponentTree":89,"./ReactInstrumentation":121,"./escapeTextContentForBrowser":167,"./reactProdInvariant":185,"./validateDOMNesting":191,"_process":40,"fbjs/lib/invariant":207,"object-assign":39}],103:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -26363,7 +23351,7 @@ function _handleChange(event) { module.exports = ReactDOMTextarea; }).call(this,require('_process')) -},{"./DisabledInputUtils":95,"./LinkedValueUtils":105,"./ReactDOMComponentTree":123,"./ReactUpdates":175,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251,"object-assign":36}],138:[function(require,module,exports){ +},{"./DisabledInputUtils":61,"./LinkedValueUtils":71,"./ReactDOMComponentTree":89,"./ReactUpdates":141,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217,"object-assign":39}],104:[function(require,module,exports){ (function (process){ /** * Copyright 2015-present, Facebook, Inc. @@ -26503,7 +23491,7 @@ module.exports = { }; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],139:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],105:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -26619,7 +23607,7 @@ var ReactDOMUnknownPropertyDevtool = { module.exports = ReactDOMUnknownPropertyDevtool; }).call(this,require('_process')) -},{"./DOMProperty":91,"./EventPluginRegistry":99,"./ReactComponentTreeDevtool":116,"_process":37,"fbjs/lib/warning":251}],140:[function(require,module,exports){ +},{"./DOMProperty":57,"./EventPluginRegistry":65,"./ReactComponentTreeDevtool":82,"_process":40,"fbjs/lib/warning":217}],106:[function(require,module,exports){ (function (process){ /** * Copyright 2016-present, Facebook, Inc. @@ -26944,7 +23932,7 @@ if (/[?&]react_perf\b/.test(url)) { module.exports = ReactDebugTool; }).call(this,require('_process')) -},{"./ReactChildrenMutationWarningDevtool":111,"./ReactComponentTreeDevtool":116,"./ReactHostOperationHistoryDevtool":151,"./ReactInvalidSetStateWarningDevTool":156,"_process":37,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/performanceNow":249,"fbjs/lib/warning":251}],141:[function(require,module,exports){ +},{"./ReactChildrenMutationWarningDevtool":77,"./ReactComponentTreeDevtool":82,"./ReactHostOperationHistoryDevtool":117,"./ReactInvalidSetStateWarningDevTool":122,"_process":40,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/performanceNow":215,"fbjs/lib/warning":217}],107:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -27013,7 +24001,7 @@ var ReactDefaultBatchingStrategy = { }; module.exports = ReactDefaultBatchingStrategy; -},{"./ReactUpdates":175,"./Transaction":193,"fbjs/lib/emptyFunction":233,"object-assign":36}],142:[function(require,module,exports){ +},{"./ReactUpdates":141,"./Transaction":159,"fbjs/lib/emptyFunction":199,"object-assign":39}],108:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -27098,7 +24086,7 @@ function inject() { module.exports = { inject: inject }; -},{"./BeforeInputEventPlugin":83,"./ChangeEventPlugin":87,"./DefaultEventPluginOrder":94,"./EnterLeaveEventPlugin":96,"./HTMLDOMPropertyConfig":103,"./ReactComponentBrowserEnvironment":114,"./ReactDOMComponent":121,"./ReactDOMComponentTree":123,"./ReactDOMEmptyComponent":126,"./ReactDOMTextComponent":136,"./ReactDOMTreeTraversal":138,"./ReactDefaultBatchingStrategy":141,"./ReactEventListener":148,"./ReactInjection":152,"./ReactReconcileTransaction":169,"./SVGDOMPropertyConfig":177,"./SelectEventPlugin":178,"./SimpleEventPlugin":179}],143:[function(require,module,exports){ +},{"./BeforeInputEventPlugin":49,"./ChangeEventPlugin":53,"./DefaultEventPluginOrder":60,"./EnterLeaveEventPlugin":62,"./HTMLDOMPropertyConfig":69,"./ReactComponentBrowserEnvironment":80,"./ReactDOMComponent":87,"./ReactDOMComponentTree":89,"./ReactDOMEmptyComponent":92,"./ReactDOMTextComponent":102,"./ReactDOMTreeTraversal":104,"./ReactDefaultBatchingStrategy":107,"./ReactEventListener":114,"./ReactInjection":118,"./ReactReconcileTransaction":135,"./SVGDOMPropertyConfig":143,"./SelectEventPlugin":144,"./SimpleEventPlugin":145}],109:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -27462,7 +24450,7 @@ ReactElement.REACT_ELEMENT_TYPE = REACT_ELEMENT_TYPE; module.exports = ReactElement; }).call(this,require('_process')) -},{"./ReactCurrentOwner":118,"./canDefineProperty":197,"_process":37,"fbjs/lib/warning":251,"object-assign":36}],144:[function(require,module,exports){ +},{"./ReactCurrentOwner":84,"./canDefineProperty":163,"_process":40,"fbjs/lib/warning":217,"object-assign":39}],110:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -27692,7 +24680,7 @@ var ReactElementValidator = { module.exports = ReactElementValidator; }).call(this,require('_process')) -},{"./ReactComponentTreeDevtool":116,"./ReactCurrentOwner":118,"./ReactElement":143,"./ReactPropTypeLocations":165,"./canDefineProperty":197,"./checkReactTypeSpec":198,"./getIteratorFn":210,"_process":37,"fbjs/lib/warning":251}],145:[function(require,module,exports){ +},{"./ReactComponentTreeDevtool":82,"./ReactCurrentOwner":84,"./ReactElement":109,"./ReactPropTypeLocations":131,"./canDefineProperty":163,"./checkReactTypeSpec":164,"./getIteratorFn":176,"_process":40,"fbjs/lib/warning":217}],111:[function(require,module,exports){ /** * Copyright 2014-present, Facebook, Inc. * All rights reserved. @@ -27723,7 +24711,7 @@ var ReactEmptyComponent = { ReactEmptyComponent.injection = ReactEmptyComponentInjection; module.exports = ReactEmptyComponent; -},{}],146:[function(require,module,exports){ +},{}],112:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -27803,7 +24791,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = ReactErrorUtils; }).call(this,require('_process')) -},{"_process":37}],147:[function(require,module,exports){ +},{"_process":40}],113:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -27837,7 +24825,7 @@ var ReactEventEmitterMixin = { }; module.exports = ReactEventEmitterMixin; -},{"./EventPluginHub":98}],148:[function(require,module,exports){ +},{"./EventPluginHub":64}],114:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -27995,7 +24983,7 @@ var ReactEventListener = { }; module.exports = ReactEventListener; -},{"./PooledClass":106,"./ReactDOMComponentTree":123,"./ReactUpdates":175,"./getEventTarget":208,"fbjs/lib/EventListener":226,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/getUnboundedScrollPosition":238,"object-assign":36}],149:[function(require,module,exports){ +},{"./PooledClass":72,"./ReactDOMComponentTree":89,"./ReactUpdates":141,"./getEventTarget":174,"fbjs/lib/EventListener":192,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/getUnboundedScrollPosition":204,"object-assign":39}],115:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -28018,7 +25006,7 @@ var ReactFeatureFlags = { }; module.exports = ReactFeatureFlags; -},{}],150:[function(require,module,exports){ +},{}],116:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -28098,7 +25086,7 @@ var ReactHostComponent = { module.exports = ReactHostComponent; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"object-assign":36}],151:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"object-assign":39}],117:[function(require,module,exports){ /** * Copyright 2016-present, Facebook, Inc. * All rights reserved. @@ -28136,7 +25124,7 @@ var ReactHostOperationHistoryDevtool = { }; module.exports = ReactHostOperationHistoryDevtool; -},{}],152:[function(require,module,exports){ +},{}],118:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -28173,7 +25161,7 @@ var ReactInjection = { }; module.exports = ReactInjection; -},{"./DOMProperty":91,"./EventPluginHub":98,"./EventPluginUtils":100,"./ReactBrowserEventEmitter":108,"./ReactClass":112,"./ReactComponentEnvironment":115,"./ReactEmptyComponent":145,"./ReactHostComponent":150,"./ReactUpdates":175}],153:[function(require,module,exports){ +},{"./DOMProperty":57,"./EventPluginHub":64,"./EventPluginUtils":66,"./ReactBrowserEventEmitter":74,"./ReactClass":78,"./ReactComponentEnvironment":81,"./ReactEmptyComponent":111,"./ReactHostComponent":116,"./ReactUpdates":141}],119:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -28298,7 +25286,7 @@ var ReactInputSelection = { }; module.exports = ReactInputSelection; -},{"./ReactDOMSelection":135,"fbjs/lib/containsNode":230,"fbjs/lib/focusNode":235,"fbjs/lib/getActiveElement":236}],154:[function(require,module,exports){ +},{"./ReactDOMSelection":101,"fbjs/lib/containsNode":196,"fbjs/lib/focusNode":201,"fbjs/lib/getActiveElement":202}],120:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -28347,7 +25335,7 @@ var ReactInstanceMap = { }; module.exports = ReactInstanceMap; -},{}],155:[function(require,module,exports){ +},{}],121:[function(require,module,exports){ (function (process){ /** * Copyright 2016-present, Facebook, Inc. @@ -28372,7 +25360,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = { debugTool: debugTool }; }).call(this,require('_process')) -},{"./ReactDebugTool":140,"_process":37}],156:[function(require,module,exports){ +},{"./ReactDebugTool":106,"_process":40}],122:[function(require,module,exports){ (function (process){ /** * Copyright 2016-present, Facebook, Inc. @@ -28412,7 +25400,7 @@ var ReactInvalidSetStateWarningDevTool = { module.exports = ReactInvalidSetStateWarningDevTool; }).call(this,require('_process')) -},{"_process":37,"fbjs/lib/warning":251}],157:[function(require,module,exports){ +},{"_process":40,"fbjs/lib/warning":217}],123:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -28463,7 +25451,7 @@ var ReactMarkupChecksum = { }; module.exports = ReactMarkupChecksum; -},{"./adler32":196}],158:[function(require,module,exports){ +},{"./adler32":162}],124:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -28966,7 +25954,7 @@ var ReactMount = { module.exports = ReactMount; }).call(this,require('_process')) -},{"./DOMLazyTree":89,"./DOMProperty":91,"./ReactBrowserEventEmitter":108,"./ReactCurrentOwner":118,"./ReactDOMComponentTree":123,"./ReactDOMContainerInfo":124,"./ReactDOMFeatureFlags":128,"./ReactElement":143,"./ReactFeatureFlags":149,"./ReactInstanceMap":154,"./ReactInstrumentation":155,"./ReactMarkupChecksum":157,"./ReactReconciler":170,"./ReactUpdateQueue":174,"./ReactUpdates":175,"./instantiateReactComponent":214,"./reactProdInvariant":219,"./setInnerHTML":221,"./shouldUpdateReactComponent":223,"_process":37,"fbjs/lib/emptyObject":234,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],159:[function(require,module,exports){ +},{"./DOMLazyTree":55,"./DOMProperty":57,"./ReactBrowserEventEmitter":74,"./ReactCurrentOwner":84,"./ReactDOMComponentTree":89,"./ReactDOMContainerInfo":90,"./ReactDOMFeatureFlags":94,"./ReactElement":109,"./ReactFeatureFlags":115,"./ReactInstanceMap":120,"./ReactInstrumentation":121,"./ReactMarkupChecksum":123,"./ReactReconciler":136,"./ReactUpdateQueue":140,"./ReactUpdates":141,"./instantiateReactComponent":180,"./reactProdInvariant":185,"./setInnerHTML":187,"./shouldUpdateReactComponent":189,"_process":40,"fbjs/lib/emptyObject":200,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],125:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -29423,7 +26411,7 @@ var ReactMultiChild = { module.exports = ReactMultiChild; }).call(this,require('_process')) -},{"./ReactChildReconciler":109,"./ReactComponentEnvironment":115,"./ReactCurrentOwner":118,"./ReactInstanceMap":154,"./ReactInstrumentation":155,"./ReactMultiChildUpdateTypes":160,"./ReactReconciler":170,"./flattenChildren":203,"./reactProdInvariant":219,"_process":37,"fbjs/lib/emptyFunction":233,"fbjs/lib/invariant":241}],160:[function(require,module,exports){ +},{"./ReactChildReconciler":75,"./ReactComponentEnvironment":81,"./ReactCurrentOwner":84,"./ReactInstanceMap":120,"./ReactInstrumentation":121,"./ReactMultiChildUpdateTypes":126,"./ReactReconciler":136,"./flattenChildren":169,"./reactProdInvariant":185,"_process":40,"fbjs/lib/emptyFunction":199,"fbjs/lib/invariant":207}],126:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -29456,7 +26444,7 @@ var ReactMultiChildUpdateTypes = keyMirror({ }); module.exports = ReactMultiChildUpdateTypes; -},{"fbjs/lib/keyMirror":244}],161:[function(require,module,exports){ +},{"fbjs/lib/keyMirror":210}],127:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -29500,7 +26488,7 @@ var ReactNodeTypes = { module.exports = ReactNodeTypes; }).call(this,require('_process')) -},{"./ReactElement":143,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],162:[function(require,module,exports){ +},{"./ReactElement":109,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],128:[function(require,module,exports){ (function (process){ /** * Copyright 2015-present, Facebook, Inc. @@ -29600,7 +26588,7 @@ var ReactNoopUpdateQueue = { module.exports = ReactNoopUpdateQueue; }).call(this,require('_process')) -},{"_process":37,"fbjs/lib/warning":251}],163:[function(require,module,exports){ +},{"_process":40,"fbjs/lib/warning":217}],129:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -29698,7 +26686,7 @@ var ReactOwner = { module.exports = ReactOwner; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],164:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],130:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -29726,7 +26714,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = ReactPropTypeLocationNames; }).call(this,require('_process')) -},{"_process":37}],165:[function(require,module,exports){ +},{"_process":40}],131:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -29749,7 +26737,7 @@ var ReactPropTypeLocations = keyMirror({ }); module.exports = ReactPropTypeLocations; -},{"fbjs/lib/keyMirror":244}],166:[function(require,module,exports){ +},{"fbjs/lib/keyMirror":210}],132:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -30170,7 +27158,7 @@ function getClassName(propValue) { module.exports = ReactPropTypes; }).call(this,require('_process')) -},{"./ReactElement":143,"./ReactPropTypeLocationNames":164,"./ReactPropTypesSecret":167,"./getIteratorFn":210,"_process":37,"fbjs/lib/emptyFunction":233,"fbjs/lib/warning":251}],167:[function(require,module,exports){ +},{"./ReactElement":109,"./ReactPropTypeLocationNames":130,"./ReactPropTypesSecret":133,"./getIteratorFn":176,"_process":40,"fbjs/lib/emptyFunction":199,"fbjs/lib/warning":217}],133:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -30187,7 +27175,7 @@ module.exports = ReactPropTypes; var ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; module.exports = ReactPropTypesSecret; -},{}],168:[function(require,module,exports){ +},{}],134:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -30230,7 +27218,7 @@ _assign(ReactPureComponent.prototype, ReactComponent.prototype); ReactPureComponent.prototype.isPureReactComponent = true; module.exports = ReactPureComponent; -},{"./ReactComponent":113,"./ReactNoopUpdateQueue":162,"fbjs/lib/emptyObject":234,"object-assign":36}],169:[function(require,module,exports){ +},{"./ReactComponent":79,"./ReactNoopUpdateQueue":128,"fbjs/lib/emptyObject":200,"object-assign":39}],135:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -30412,7 +27400,7 @@ PooledClass.addPoolingTo(ReactReconcileTransaction); module.exports = ReactReconcileTransaction; }).call(this,require('_process')) -},{"./CallbackQueue":86,"./PooledClass":106,"./ReactBrowserEventEmitter":108,"./ReactInputSelection":153,"./ReactInstrumentation":155,"./ReactUpdateQueue":174,"./Transaction":193,"_process":37,"object-assign":36}],170:[function(require,module,exports){ +},{"./CallbackQueue":52,"./PooledClass":72,"./ReactBrowserEventEmitter":74,"./ReactInputSelection":119,"./ReactInstrumentation":121,"./ReactUpdateQueue":140,"./Transaction":159,"_process":40,"object-assign":39}],136:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -30590,7 +27578,7 @@ var ReactReconciler = { module.exports = ReactReconciler; }).call(this,require('_process')) -},{"./ReactInstrumentation":155,"./ReactRef":171,"_process":37,"fbjs/lib/warning":251}],171:[function(require,module,exports){ +},{"./ReactInstrumentation":121,"./ReactRef":137,"_process":40,"fbjs/lib/warning":217}],137:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -30671,7 +27659,7 @@ ReactRef.detachRefs = function (instance, element) { }; module.exports = ReactRef; -},{"./ReactOwner":163}],172:[function(require,module,exports){ +},{"./ReactOwner":129}],138:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -30765,7 +27753,7 @@ PooledClass.addPoolingTo(ReactServerRenderingTransaction); module.exports = ReactServerRenderingTransaction; }).call(this,require('_process')) -},{"./PooledClass":106,"./ReactInstrumentation":155,"./ReactServerUpdateQueue":173,"./Transaction":193,"_process":37,"object-assign":36}],173:[function(require,module,exports){ +},{"./PooledClass":72,"./ReactInstrumentation":121,"./ReactServerUpdateQueue":139,"./Transaction":159,"_process":40,"object-assign":39}],139:[function(require,module,exports){ (function (process){ /** * Copyright 2015-present, Facebook, Inc. @@ -30910,7 +27898,7 @@ var ReactServerUpdateQueue = function () { module.exports = ReactServerUpdateQueue; }).call(this,require('_process')) -},{"./ReactUpdateQueue":174,"./Transaction":193,"_process":37,"fbjs/lib/warning":251}],174:[function(require,module,exports){ +},{"./ReactUpdateQueue":140,"./Transaction":159,"_process":40,"fbjs/lib/warning":217}],140:[function(require,module,exports){ (function (process){ /** * Copyright 2015-present, Facebook, Inc. @@ -31140,7 +28128,7 @@ var ReactUpdateQueue = { module.exports = ReactUpdateQueue; }).call(this,require('_process')) -},{"./ReactCurrentOwner":118,"./ReactInstanceMap":154,"./ReactInstrumentation":155,"./ReactUpdates":175,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],175:[function(require,module,exports){ +},{"./ReactCurrentOwner":84,"./ReactInstanceMap":120,"./ReactInstrumentation":121,"./ReactUpdates":141,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],141:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -31395,7 +28383,7 @@ var ReactUpdates = { module.exports = ReactUpdates; }).call(this,require('_process')) -},{"./CallbackQueue":86,"./PooledClass":106,"./ReactFeatureFlags":149,"./ReactReconciler":170,"./Transaction":193,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"object-assign":36}],176:[function(require,module,exports){ +},{"./CallbackQueue":52,"./PooledClass":72,"./ReactFeatureFlags":115,"./ReactReconciler":136,"./Transaction":159,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"object-assign":39}],142:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -31410,7 +28398,7 @@ module.exports = ReactUpdates; 'use strict'; module.exports = '15.3.0'; -},{}],177:[function(require,module,exports){ +},{}],143:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -31713,7 +28701,7 @@ Object.keys(ATTRS).forEach(function (key) { }); module.exports = SVGDOMPropertyConfig; -},{}],178:[function(require,module,exports){ +},{}],144:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -31910,7 +28898,7 @@ var SelectEventPlugin = { }; module.exports = SelectEventPlugin; -},{"./EventConstants":97,"./EventPropagators":101,"./ReactDOMComponentTree":123,"./ReactInputSelection":153,"./SyntheticEvent":184,"./isTextInputElement":216,"fbjs/lib/ExecutionEnvironment":227,"fbjs/lib/getActiveElement":236,"fbjs/lib/keyOf":245,"fbjs/lib/shallowEqual":250}],179:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPropagators":67,"./ReactDOMComponentTree":89,"./ReactInputSelection":119,"./SyntheticEvent":150,"./isTextInputElement":182,"fbjs/lib/ExecutionEnvironment":193,"fbjs/lib/getActiveElement":202,"fbjs/lib/keyOf":211,"fbjs/lib/shallowEqual":216}],145:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -32547,7 +29535,7 @@ var SimpleEventPlugin = { module.exports = SimpleEventPlugin; }).call(this,require('_process')) -},{"./EventConstants":97,"./EventPropagators":101,"./ReactDOMComponentTree":123,"./SyntheticAnimationEvent":180,"./SyntheticClipboardEvent":181,"./SyntheticDragEvent":183,"./SyntheticEvent":184,"./SyntheticFocusEvent":185,"./SyntheticKeyboardEvent":187,"./SyntheticMouseEvent":188,"./SyntheticTouchEvent":189,"./SyntheticTransitionEvent":190,"./SyntheticUIEvent":191,"./SyntheticWheelEvent":192,"./getEventCharCode":205,"./reactProdInvariant":219,"_process":37,"fbjs/lib/EventListener":226,"fbjs/lib/emptyFunction":233,"fbjs/lib/invariant":241,"fbjs/lib/keyOf":245}],180:[function(require,module,exports){ +},{"./EventConstants":63,"./EventPropagators":67,"./ReactDOMComponentTree":89,"./SyntheticAnimationEvent":146,"./SyntheticClipboardEvent":147,"./SyntheticDragEvent":149,"./SyntheticEvent":150,"./SyntheticFocusEvent":151,"./SyntheticKeyboardEvent":153,"./SyntheticMouseEvent":154,"./SyntheticTouchEvent":155,"./SyntheticTransitionEvent":156,"./SyntheticUIEvent":157,"./SyntheticWheelEvent":158,"./getEventCharCode":171,"./reactProdInvariant":185,"_process":40,"fbjs/lib/EventListener":192,"fbjs/lib/emptyFunction":199,"fbjs/lib/invariant":207,"fbjs/lib/keyOf":211}],146:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -32587,7 +29575,7 @@ function SyntheticAnimationEvent(dispatchConfig, dispatchMarker, nativeEvent, na SyntheticEvent.augmentClass(SyntheticAnimationEvent, AnimationEventInterface); module.exports = SyntheticAnimationEvent; -},{"./SyntheticEvent":184}],181:[function(require,module,exports){ +},{"./SyntheticEvent":150}],147:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -32626,7 +29614,7 @@ function SyntheticClipboardEvent(dispatchConfig, dispatchMarker, nativeEvent, na SyntheticEvent.augmentClass(SyntheticClipboardEvent, ClipboardEventInterface); module.exports = SyntheticClipboardEvent; -},{"./SyntheticEvent":184}],182:[function(require,module,exports){ +},{"./SyntheticEvent":150}],148:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -32663,7 +29651,7 @@ function SyntheticCompositionEvent(dispatchConfig, dispatchMarker, nativeEvent, SyntheticEvent.augmentClass(SyntheticCompositionEvent, CompositionEventInterface); module.exports = SyntheticCompositionEvent; -},{"./SyntheticEvent":184}],183:[function(require,module,exports){ +},{"./SyntheticEvent":150}],149:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -32700,7 +29688,7 @@ function SyntheticDragEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeE SyntheticMouseEvent.augmentClass(SyntheticDragEvent, DragEventInterface); module.exports = SyntheticDragEvent; -},{"./SyntheticMouseEvent":188}],184:[function(require,module,exports){ +},{"./SyntheticMouseEvent":154}],150:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -32964,7 +29952,7 @@ function getPooledWarningPropertyDefinition(propName, getVal) { } }).call(this,require('_process')) -},{"./PooledClass":106,"_process":37,"fbjs/lib/emptyFunction":233,"fbjs/lib/warning":251,"object-assign":36}],185:[function(require,module,exports){ +},{"./PooledClass":72,"_process":40,"fbjs/lib/emptyFunction":199,"fbjs/lib/warning":217,"object-assign":39}],151:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33001,7 +29989,7 @@ function SyntheticFocusEvent(dispatchConfig, dispatchMarker, nativeEvent, native SyntheticUIEvent.augmentClass(SyntheticFocusEvent, FocusEventInterface); module.exports = SyntheticFocusEvent; -},{"./SyntheticUIEvent":191}],186:[function(require,module,exports){ +},{"./SyntheticUIEvent":157}],152:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33039,7 +30027,7 @@ function SyntheticInputEvent(dispatchConfig, dispatchMarker, nativeEvent, native SyntheticEvent.augmentClass(SyntheticInputEvent, InputEventInterface); module.exports = SyntheticInputEvent; -},{"./SyntheticEvent":184}],187:[function(require,module,exports){ +},{"./SyntheticEvent":150}],153:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33124,7 +30112,7 @@ function SyntheticKeyboardEvent(dispatchConfig, dispatchMarker, nativeEvent, nat SyntheticUIEvent.augmentClass(SyntheticKeyboardEvent, KeyboardEventInterface); module.exports = SyntheticKeyboardEvent; -},{"./SyntheticUIEvent":191,"./getEventCharCode":205,"./getEventKey":206,"./getEventModifierState":207}],188:[function(require,module,exports){ +},{"./SyntheticUIEvent":157,"./getEventCharCode":171,"./getEventKey":172,"./getEventModifierState":173}],154:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33197,7 +30185,7 @@ function SyntheticMouseEvent(dispatchConfig, dispatchMarker, nativeEvent, native SyntheticUIEvent.augmentClass(SyntheticMouseEvent, MouseEventInterface); module.exports = SyntheticMouseEvent; -},{"./SyntheticUIEvent":191,"./ViewportMetrics":194,"./getEventModifierState":207}],189:[function(require,module,exports){ +},{"./SyntheticUIEvent":157,"./ViewportMetrics":160,"./getEventModifierState":173}],155:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33243,7 +30231,7 @@ function SyntheticTouchEvent(dispatchConfig, dispatchMarker, nativeEvent, native SyntheticUIEvent.augmentClass(SyntheticTouchEvent, TouchEventInterface); module.exports = SyntheticTouchEvent; -},{"./SyntheticUIEvent":191,"./getEventModifierState":207}],190:[function(require,module,exports){ +},{"./SyntheticUIEvent":157,"./getEventModifierState":173}],156:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33283,7 +30271,7 @@ function SyntheticTransitionEvent(dispatchConfig, dispatchMarker, nativeEvent, n SyntheticEvent.augmentClass(SyntheticTransitionEvent, TransitionEventInterface); module.exports = SyntheticTransitionEvent; -},{"./SyntheticEvent":184}],191:[function(require,module,exports){ +},{"./SyntheticEvent":150}],157:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33343,7 +30331,7 @@ function SyntheticUIEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEve SyntheticEvent.augmentClass(SyntheticUIEvent, UIEventInterface); module.exports = SyntheticUIEvent; -},{"./SyntheticEvent":184,"./getEventTarget":208}],192:[function(require,module,exports){ +},{"./SyntheticEvent":150,"./getEventTarget":174}],158:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33398,7 +30386,7 @@ function SyntheticWheelEvent(dispatchConfig, dispatchMarker, nativeEvent, native SyntheticMouseEvent.augmentClass(SyntheticWheelEvent, WheelEventInterface); module.exports = SyntheticWheelEvent; -},{"./SyntheticMouseEvent":188}],193:[function(require,module,exports){ +},{"./SyntheticMouseEvent":154}],159:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -33635,7 +30623,7 @@ var Transaction = { module.exports = Transaction; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],194:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],160:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33663,7 +30651,7 @@ var ViewportMetrics = { }; module.exports = ViewportMetrics; -},{}],195:[function(require,module,exports){ +},{}],161:[function(require,module,exports){ (function (process){ /** * Copyright 2014-present, Facebook, Inc. @@ -33725,7 +30713,7 @@ function accumulateInto(current, next) { module.exports = accumulateInto; }).call(this,require('_process')) -},{"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],196:[function(require,module,exports){ +},{"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],162:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33770,7 +30758,7 @@ function adler32(data) { } module.exports = adler32; -},{}],197:[function(require,module,exports){ +},{}],163:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -33798,7 +30786,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = canDefineProperty; }).call(this,require('_process')) -},{"_process":37}],198:[function(require,module,exports){ +},{"_process":40}],164:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -33889,7 +30877,7 @@ function checkReactTypeSpec(typeSpecs, values, location, componentName, element, module.exports = checkReactTypeSpec; }).call(this,require('_process')) -},{"./ReactComponentTreeDevtool":116,"./ReactPropTypeLocationNames":164,"./ReactPropTypesSecret":167,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],199:[function(require,module,exports){ +},{"./ReactComponentTreeDevtool":82,"./ReactPropTypeLocationNames":130,"./ReactPropTypesSecret":133,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],165:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -33922,7 +30910,7 @@ var createMicrosoftUnsafeLocalFunction = function (func) { }; module.exports = createMicrosoftUnsafeLocalFunction; -},{}],200:[function(require,module,exports){ +},{}],166:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -34005,7 +30993,7 @@ function dangerousStyleValue(name, value, component) { module.exports = dangerousStyleValue; }).call(this,require('_process')) -},{"./CSSProperty":84,"_process":37,"fbjs/lib/warning":251}],201:[function(require,module,exports){ +},{"./CSSProperty":50,"_process":40,"fbjs/lib/warning":217}],167:[function(require,module,exports){ /** * Copyright 2016-present, Facebook, Inc. * All rights reserved. @@ -34128,7 +31116,7 @@ function escapeTextContentForBrowser(text) { } module.exports = escapeTextContentForBrowser; -},{}],202:[function(require,module,exports){ +},{}],168:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -34192,7 +31180,7 @@ function findDOMNode(componentOrElement) { module.exports = findDOMNode; }).call(this,require('_process')) -},{"./ReactCurrentOwner":118,"./ReactDOMComponentTree":123,"./ReactInstanceMap":154,"./getHostComponentFromComposite":209,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],203:[function(require,module,exports){ +},{"./ReactCurrentOwner":84,"./ReactDOMComponentTree":89,"./ReactInstanceMap":120,"./getHostComponentFromComposite":175,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],169:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -34270,7 +31258,7 @@ function flattenChildren(children, selfDebugID) { module.exports = flattenChildren; }).call(this,require('_process')) -},{"./KeyEscapeUtils":104,"./ReactComponentTreeDevtool":116,"./traverseAllChildren":224,"_process":37,"fbjs/lib/warning":251}],204:[function(require,module,exports){ +},{"./KeyEscapeUtils":70,"./ReactComponentTreeDevtool":82,"./traverseAllChildren":190,"_process":40,"fbjs/lib/warning":217}],170:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34302,7 +31290,7 @@ function forEachAccumulated(arr, cb, scope) { } module.exports = forEachAccumulated; -},{}],205:[function(require,module,exports){ +},{}],171:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34353,7 +31341,7 @@ function getEventCharCode(nativeEvent) { } module.exports = getEventCharCode; -},{}],206:[function(require,module,exports){ +},{}],172:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34456,7 +31444,7 @@ function getEventKey(nativeEvent) { } module.exports = getEventKey; -},{"./getEventCharCode":205}],207:[function(require,module,exports){ +},{"./getEventCharCode":171}],173:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34500,7 +31488,7 @@ function getEventModifierState(nativeEvent) { } module.exports = getEventModifierState; -},{}],208:[function(require,module,exports){ +},{}],174:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34536,7 +31524,7 @@ function getEventTarget(nativeEvent) { } module.exports = getEventTarget; -},{}],209:[function(require,module,exports){ +},{}],175:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34567,7 +31555,7 @@ function getHostComponentFromComposite(inst) { } module.exports = getHostComponentFromComposite; -},{"./ReactNodeTypes":161}],210:[function(require,module,exports){ +},{"./ReactNodeTypes":127}],176:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34609,7 +31597,7 @@ function getIteratorFn(maybeIterable) { } module.exports = getIteratorFn; -},{}],211:[function(require,module,exports){ +},{}],177:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34684,7 +31672,7 @@ function getNodeForCharacterOffset(root, offset) { } module.exports = getNodeForCharacterOffset; -},{}],212:[function(require,module,exports){ +},{}],178:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34718,7 +31706,7 @@ function getTextContentAccessor() { } module.exports = getTextContentAccessor; -},{"fbjs/lib/ExecutionEnvironment":227}],213:[function(require,module,exports){ +},{"fbjs/lib/ExecutionEnvironment":193}],179:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -34820,7 +31808,7 @@ function getVendorPrefixedEventName(eventName) { } module.exports = getVendorPrefixedEventName; -},{"fbjs/lib/ExecutionEnvironment":227}],214:[function(require,module,exports){ +},{"fbjs/lib/ExecutionEnvironment":193}],180:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -34970,7 +31958,7 @@ function instantiateReactComponent(node, shouldHaveDebugID) { module.exports = instantiateReactComponent; }).call(this,require('_process')) -},{"./ReactCompositeComponent":117,"./ReactEmptyComponent":145,"./ReactHostComponent":150,"./ReactInstrumentation":155,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251,"object-assign":36}],215:[function(require,module,exports){ +},{"./ReactCompositeComponent":83,"./ReactEmptyComponent":111,"./ReactHostComponent":116,"./ReactInstrumentation":121,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217,"object-assign":39}],181:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35031,7 +32019,7 @@ function isEventSupported(eventNameSuffix, capture) { } module.exports = isEventSupported; -},{"fbjs/lib/ExecutionEnvironment":227}],216:[function(require,module,exports){ +},{"fbjs/lib/ExecutionEnvironment":193}],182:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35083,7 +32071,7 @@ function isTextInputElement(elem) { } module.exports = isTextInputElement; -},{}],217:[function(require,module,exports){ +},{}],183:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -35125,7 +32113,7 @@ function onlyChild(children) { module.exports = onlyChild; }).call(this,require('_process')) -},{"./ReactElement":143,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241}],218:[function(require,module,exports){ +},{"./ReactElement":109,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207}],184:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35152,7 +32140,7 @@ function quoteAttributeValueForBrowser(value) { } module.exports = quoteAttributeValueForBrowser; -},{"./escapeTextContentForBrowser":201}],219:[function(require,module,exports){ +},{"./escapeTextContentForBrowser":167}],185:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -35192,7 +32180,7 @@ function reactProdInvariant(code) { } module.exports = reactProdInvariant; -},{}],220:[function(require,module,exports){ +},{}],186:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35209,7 +32197,7 @@ module.exports = reactProdInvariant; var ReactMount = require('./ReactMount'); module.exports = ReactMount.renderSubtreeIntoContainer; -},{"./ReactMount":158}],221:[function(require,module,exports){ +},{"./ReactMount":124}],187:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35308,7 +32296,7 @@ if (ExecutionEnvironment.canUseDOM) { } module.exports = setInnerHTML; -},{"./DOMNamespaces":90,"./createMicrosoftUnsafeLocalFunction":199,"fbjs/lib/ExecutionEnvironment":227}],222:[function(require,module,exports){ +},{"./DOMNamespaces":56,"./createMicrosoftUnsafeLocalFunction":165,"fbjs/lib/ExecutionEnvironment":193}],188:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35357,7 +32345,7 @@ if (ExecutionEnvironment.canUseDOM) { } module.exports = setTextContent; -},{"./escapeTextContentForBrowser":201,"./setInnerHTML":221,"fbjs/lib/ExecutionEnvironment":227}],223:[function(require,module,exports){ +},{"./escapeTextContentForBrowser":167,"./setInnerHTML":187,"fbjs/lib/ExecutionEnvironment":193}],189:[function(require,module,exports){ /** * Copyright 2013-present, Facebook, Inc. * All rights reserved. @@ -35400,7 +32388,7 @@ function shouldUpdateReactComponent(prevElement, nextElement) { } module.exports = shouldUpdateReactComponent; -},{}],224:[function(require,module,exports){ +},{}],190:[function(require,module,exports){ (function (process){ /** * Copyright 2013-present, Facebook, Inc. @@ -35571,7 +32559,7 @@ function traverseAllChildren(children, callback, traverseContext) { module.exports = traverseAllChildren; }).call(this,require('_process')) -},{"./KeyEscapeUtils":104,"./ReactCurrentOwner":118,"./ReactElement":143,"./getIteratorFn":210,"./reactProdInvariant":219,"_process":37,"fbjs/lib/invariant":241,"fbjs/lib/warning":251}],225:[function(require,module,exports){ +},{"./KeyEscapeUtils":70,"./ReactCurrentOwner":84,"./ReactElement":109,"./getIteratorFn":176,"./reactProdInvariant":185,"_process":40,"fbjs/lib/invariant":207,"fbjs/lib/warning":217}],191:[function(require,module,exports){ (function (process){ /** * Copyright 2015-present, Facebook, Inc. @@ -35944,7 +32932,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = validateDOMNesting; }).call(this,require('_process')) -},{"_process":37,"fbjs/lib/emptyFunction":233,"fbjs/lib/warning":251,"object-assign":36}],226:[function(require,module,exports){ +},{"_process":40,"fbjs/lib/emptyFunction":199,"fbjs/lib/warning":217,"object-assign":39}],192:[function(require,module,exports){ (function (process){ 'use strict'; @@ -36031,7 +33019,7 @@ var EventListener = { module.exports = EventListener; }).call(this,require('_process')) -},{"./emptyFunction":233,"_process":37}],227:[function(require,module,exports){ +},{"./emptyFunction":199,"_process":40}],193:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36067,7 +33055,7 @@ var ExecutionEnvironment = { }; module.exports = ExecutionEnvironment; -},{}],228:[function(require,module,exports){ +},{}],194:[function(require,module,exports){ "use strict"; /** @@ -36099,7 +33087,7 @@ function camelize(string) { } module.exports = camelize; -},{}],229:[function(require,module,exports){ +},{}],195:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36139,7 +33127,7 @@ function camelizeStyleName(string) { } module.exports = camelizeStyleName; -},{"./camelize":228}],230:[function(require,module,exports){ +},{"./camelize":194}],196:[function(require,module,exports){ 'use strict'; /** @@ -36179,7 +33167,7 @@ function containsNode(outerNode, innerNode) { } module.exports = containsNode; -},{"./isTextNode":243}],231:[function(require,module,exports){ +},{"./isTextNode":209}],197:[function(require,module,exports){ (function (process){ 'use strict'; @@ -36309,7 +33297,7 @@ function createArrayFromMixed(obj) { module.exports = createArrayFromMixed; }).call(this,require('_process')) -},{"./invariant":241,"_process":37}],232:[function(require,module,exports){ +},{"./invariant":207,"_process":40}],198:[function(require,module,exports){ (function (process){ 'use strict'; @@ -36396,7 +33384,7 @@ function createNodesFromMarkup(markup, handleScript) { module.exports = createNodesFromMarkup; }).call(this,require('_process')) -},{"./ExecutionEnvironment":227,"./createArrayFromMixed":231,"./getMarkupWrap":237,"./invariant":241,"_process":37}],233:[function(require,module,exports){ +},{"./ExecutionEnvironment":193,"./createArrayFromMixed":197,"./getMarkupWrap":203,"./invariant":207,"_process":40}],199:[function(require,module,exports){ "use strict"; /** @@ -36435,7 +33423,7 @@ emptyFunction.thatReturnsArgument = function (arg) { }; module.exports = emptyFunction; -},{}],234:[function(require,module,exports){ +},{}],200:[function(require,module,exports){ (function (process){ /** * Copyright (c) 2013-present, Facebook, Inc. @@ -36458,7 +33446,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = emptyObject; }).call(this,require('_process')) -},{"_process":37}],235:[function(require,module,exports){ +},{"_process":40}],201:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36485,7 +33473,7 @@ function focusNode(node) { } module.exports = focusNode; -},{}],236:[function(require,module,exports){ +},{}],202:[function(require,module,exports){ 'use strict'; /** @@ -36520,7 +33508,7 @@ function getActiveElement() /*?DOMElement*/{ } module.exports = getActiveElement; -},{}],237:[function(require,module,exports){ +},{}],203:[function(require,module,exports){ (function (process){ 'use strict'; @@ -36618,7 +33606,7 @@ function getMarkupWrap(nodeName) { module.exports = getMarkupWrap; }).call(this,require('_process')) -},{"./ExecutionEnvironment":227,"./invariant":241,"_process":37}],238:[function(require,module,exports){ +},{"./ExecutionEnvironment":193,"./invariant":207,"_process":40}],204:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36657,7 +33645,7 @@ function getUnboundedScrollPosition(scrollable) { } module.exports = getUnboundedScrollPosition; -},{}],239:[function(require,module,exports){ +},{}],205:[function(require,module,exports){ 'use strict'; /** @@ -36690,7 +33678,7 @@ function hyphenate(string) { } module.exports = hyphenate; -},{}],240:[function(require,module,exports){ +},{}],206:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36729,7 +33717,7 @@ function hyphenateStyleName(string) { } module.exports = hyphenateStyleName; -},{"./hyphenate":239}],241:[function(require,module,exports){ +},{"./hyphenate":205}],207:[function(require,module,exports){ (function (process){ /** * Copyright (c) 2013-present, Facebook, Inc. @@ -36782,7 +33770,7 @@ function invariant(condition, format, a, b, c, d, e, f) { module.exports = invariant; }).call(this,require('_process')) -},{"_process":37}],242:[function(require,module,exports){ +},{"_process":40}],208:[function(require,module,exports){ 'use strict'; /** @@ -36805,7 +33793,7 @@ function isNode(object) { } module.exports = isNode; -},{}],243:[function(require,module,exports){ +},{}],209:[function(require,module,exports){ 'use strict'; /** @@ -36830,7 +33818,7 @@ function isTextNode(object) { } module.exports = isTextNode; -},{"./isNode":242}],244:[function(require,module,exports){ +},{"./isNode":208}],210:[function(require,module,exports){ (function (process){ /** * Copyright (c) 2013-present, Facebook, Inc. @@ -36881,7 +33869,7 @@ var keyMirror = function keyMirror(obj) { module.exports = keyMirror; }).call(this,require('_process')) -},{"./invariant":241,"_process":37}],245:[function(require,module,exports){ +},{"./invariant":207,"_process":40}],211:[function(require,module,exports){ "use strict"; /** @@ -36916,7 +33904,7 @@ var keyOf = function keyOf(oneKeyObj) { }; module.exports = keyOf; -},{}],246:[function(require,module,exports){ +},{}],212:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36967,7 +33955,7 @@ function mapObject(object, callback, context) { } module.exports = mapObject; -},{}],247:[function(require,module,exports){ +},{}],213:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -36997,7 +33985,7 @@ function memoizeStringOnly(callback) { } module.exports = memoizeStringOnly; -},{}],248:[function(require,module,exports){ +},{}],214:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -37020,7 +34008,7 @@ if (ExecutionEnvironment.canUseDOM) { } module.exports = performance || {}; -},{"./ExecutionEnvironment":227}],249:[function(require,module,exports){ +},{"./ExecutionEnvironment":193}],215:[function(require,module,exports){ 'use strict'; /** @@ -37054,7 +34042,7 @@ if (performance.now) { } module.exports = performanceNow; -},{"./performance":248}],250:[function(require,module,exports){ +},{"./performance":214}],216:[function(require,module,exports){ /** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. @@ -37121,7 +34109,7 @@ function shallowEqual(objA, objB) { } module.exports = shallowEqual; -},{}],251:[function(require,module,exports){ +},{}],217:[function(require,module,exports){ (function (process){ /** * Copyright 2014-2015, Facebook, Inc. @@ -37181,7 +34169,7 @@ if (process.env.NODE_ENV !== 'production') { module.exports = warning; }).call(this,require('_process')) -},{"./emptyFunction":233,"_process":37}],252:[function(require,module,exports){ +},{"./emptyFunction":199,"_process":40}],218:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -37240,7 +34228,7 @@ function applyMiddleware() { }; }; } -},{"./compose":255}],253:[function(require,module,exports){ +},{"./compose":221}],219:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -37292,7 +34280,7 @@ function bindActionCreators(actionCreators, dispatch) { } return boundActionCreators; } -},{}],254:[function(require,module,exports){ +},{}],220:[function(require,module,exports){ (function (process){ 'use strict'; @@ -37423,7 +34411,7 @@ function combineReducers(reducers) { } }).call(this,require('_process')) -},{"./createStore":256,"./utils/warning":257,"_process":37,"lodash/isPlainObject":35}],255:[function(require,module,exports){ +},{"./createStore":222,"./utils/warning":223,"_process":40,"lodash/isPlainObject":38}],221:[function(require,module,exports){ "use strict"; exports.__esModule = true; @@ -37464,7 +34452,7 @@ function compose() { if (typeof _ret === "object") return _ret.v; } } -},{}],256:[function(require,module,exports){ +},{}],222:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -37727,7 +34715,7 @@ function createStore(reducer, initialState, enhancer) { replaceReducer: replaceReducer }, _ref2[_symbolObservable2["default"]] = observable, _ref2; } -},{"lodash/isPlainObject":35,"symbol-observable":259}],257:[function(require,module,exports){ +},{"lodash/isPlainObject":38,"symbol-observable":225}],223:[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -37753,7 +34741,7 @@ function warning(message) { } catch (e) {} /* eslint-enable no-empty */ } -},{}],258:[function(require,module,exports){ +},{}],224:[function(require,module,exports){ 'use strict'; module.exports = function (str) { return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { @@ -37761,7 +34749,7 @@ module.exports = function (str) { }); }; -},{}],259:[function(require,module,exports){ +},{}],225:[function(require,module,exports){ (function (global){ /* global window */ 'use strict'; @@ -37770,7 +34758,7 @@ module.exports = require('./ponyfill')(global || window || this); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./ponyfill":260}],260:[function(require,module,exports){ +},{"./ponyfill":226}],226:[function(require,module,exports){ 'use strict'; module.exports = function symbolObservablePonyfill(root) { @@ -37791,72 +34779,7 @@ module.exports = function symbolObservablePonyfill(root) { return result; }; -},{}],261:[function(require,module,exports){ -(function (process){ -/** - * Copyright 2014-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -/** - * Similar to invariant but only logs a warning if the condition is not met. - * This can be used to log issues in development environments in critical - * paths. Removing the logging code for production environments will keep the - * same logic and follow the same code paths. - */ - -var warning = function() {}; - -if (process.env.NODE_ENV !== 'production') { - warning = function(condition, format, args) { - var len = arguments.length; - args = new Array(len > 2 ? len - 2 : 0); - for (var key = 2; key < len; key++) { - args[key - 2] = arguments[key]; - } - if (format === undefined) { - throw new Error( - '`warning(condition, format, ...args)` requires a warning ' + - 'message argument' - ); - } - - if (format.length < 10 || (/^[s\W]*$/).test(format)) { - throw new Error( - 'The warning format should be able to uniquely identify this ' + - 'warning. Please, use a more descriptive format than: ' + format - ); - } - - if (!condition) { - var argIndex = 0; - var message = 'Warning: ' + - format.replace(/%s/g, function() { - return args[argIndex++]; - }); - if (typeof console !== 'undefined') { - console.error(message); - } - try { - // This error was thrown as a convenience so that you can use this stack - // to find the callsite that caused this warning to fire. - throw new Error(message); - } catch(x) {} - } - }; -} - -module.exports = warning; - -}).call(this,require('_process')) - -},{"_process":37}],"classnames":[function(require,module,exports){ +},{}],"classnames":[function(require,module,exports){ /*! Copyright (c) 2016 Jed Watson. Licensed under the MIT License (MIT), see @@ -37918,7 +34841,79 @@ module.exports = warning; module.exports.Dispatcher = require('./lib/Dispatcher'); -},{"./lib/Dispatcher":6}],"lodash":[function(require,module,exports){ +},{"./lib/Dispatcher":6}],"history":[function(require,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _deprecate = require('./deprecate'); + +var _deprecate2 = _interopRequireDefault(_deprecate); + +var _createLocation2 = require('./createLocation'); + +var _createLocation3 = _interopRequireDefault(_createLocation2); + +var _createBrowserHistory = require('./createBrowserHistory'); + +var _createBrowserHistory2 = _interopRequireDefault(_createBrowserHistory); + +exports.createHistory = _createBrowserHistory2['default']; + +var _createHashHistory2 = require('./createHashHistory'); + +var _createHashHistory3 = _interopRequireDefault(_createHashHistory2); + +exports.createHashHistory = _createHashHistory3['default']; + +var _createMemoryHistory2 = require('./createMemoryHistory'); + +var _createMemoryHistory3 = _interopRequireDefault(_createMemoryHistory2); + +exports.createMemoryHistory = _createMemoryHistory3['default']; + +var _useBasename2 = require('./useBasename'); + +var _useBasename3 = _interopRequireDefault(_useBasename2); + +exports.useBasename = _useBasename3['default']; + +var _useBeforeUnload2 = require('./useBeforeUnload'); + +var _useBeforeUnload3 = _interopRequireDefault(_useBeforeUnload2); + +exports.useBeforeUnload = _useBeforeUnload3['default']; + +var _useQueries2 = require('./useQueries'); + +var _useQueries3 = _interopRequireDefault(_useQueries2); + +exports.useQueries = _useQueries3['default']; + +var _Actions2 = require('./Actions'); + +var _Actions3 = _interopRequireDefault(_Actions2); + +exports.Actions = _Actions3['default']; + +// deprecated + +var _enableBeforeUnload2 = require('./enableBeforeUnload'); + +var _enableBeforeUnload3 = _interopRequireDefault(_enableBeforeUnload2); + +exports.enableBeforeUnload = _enableBeforeUnload3['default']; + +var _enableQueries2 = require('./enableQueries'); + +var _enableQueries3 = _interopRequireDefault(_enableQueries2); + +exports.enableQueries = _enableQueries3['default']; +var createLocation = _deprecate2['default'](_createLocation3['default'], 'Using createLocation without a history instance is deprecated; please use history.createLocation instead'); +exports.createLocation = createLocation; +},{"./Actions":7,"./createBrowserHistory":13,"./createHashHistory":15,"./createLocation":17,"./createMemoryHistory":18,"./deprecate":19,"./enableBeforeUnload":20,"./enableQueries":21,"./useBasename":23,"./useBeforeUnload":24,"./useQueries":25}],"lodash":[function(require,module,exports){ (function (global){ /** * @license @@ -54614,12 +51609,12 @@ var CodeMirror = React.createClass({ }); module.exports = CodeMirror; -},{"classnames":"classnames","codemirror":1,"lodash.debounce":27,"react":"react"}],"react-dom":[function(require,module,exports){ +},{"classnames":"classnames","codemirror":1,"lodash.debounce":30,"react":"react"}],"react-dom":[function(require,module,exports){ 'use strict'; module.exports = require('react/lib/ReactDOM'); -},{"react/lib/ReactDOM":119}],"react-redux":[function(require,module,exports){ +},{"react/lib/ReactDOM":85}],"react-redux":[function(require,module,exports){ 'use strict'; exports.__esModule = true; @@ -54637,169 +51632,12 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "d exports.Provider = _Provider2["default"]; exports.connect = _connect2["default"]; -},{"./components/Provider":39,"./components/connect":40}],"react-router":[function(require,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.createMemoryHistory = exports.hashHistory = exports.browserHistory = exports.applyRouterMiddleware = exports.formatPattern = exports.useRouterHistory = exports.match = exports.routerShape = exports.locationShape = exports.PropTypes = exports.RoutingContext = exports.RouterContext = exports.createRoutes = exports.useRoutes = exports.RouteContext = exports.Lifecycle = exports.History = exports.Route = exports.Redirect = exports.IndexRoute = exports.IndexRedirect = exports.withRouter = exports.IndexLink = exports.Link = exports.Router = undefined; - -var _RouteUtils = require('./RouteUtils'); - -Object.defineProperty(exports, 'createRoutes', { - enumerable: true, - get: function get() { - return _RouteUtils.createRoutes; - } -}); - -var _PropTypes2 = require('./PropTypes'); - -Object.defineProperty(exports, 'locationShape', { - enumerable: true, - get: function get() { - return _PropTypes2.locationShape; - } -}); -Object.defineProperty(exports, 'routerShape', { - enumerable: true, - get: function get() { - return _PropTypes2.routerShape; - } -}); - -var _PatternUtils = require('./PatternUtils'); - -Object.defineProperty(exports, 'formatPattern', { - enumerable: true, - get: function get() { - return _PatternUtils.formatPattern; - } -}); - -var _Router2 = require('./Router'); - -var _Router3 = _interopRequireDefault(_Router2); - -var _Link2 = require('./Link'); - -var _Link3 = _interopRequireDefault(_Link2); - -var _IndexLink2 = require('./IndexLink'); - -var _IndexLink3 = _interopRequireDefault(_IndexLink2); - -var _withRouter2 = require('./withRouter'); - -var _withRouter3 = _interopRequireDefault(_withRouter2); - -var _IndexRedirect2 = require('./IndexRedirect'); - -var _IndexRedirect3 = _interopRequireDefault(_IndexRedirect2); - -var _IndexRoute2 = require('./IndexRoute'); - -var _IndexRoute3 = _interopRequireDefault(_IndexRoute2); - -var _Redirect2 = require('./Redirect'); - -var _Redirect3 = _interopRequireDefault(_Redirect2); - -var _Route2 = require('./Route'); - -var _Route3 = _interopRequireDefault(_Route2); - -var _History2 = require('./History'); - -var _History3 = _interopRequireDefault(_History2); - -var _Lifecycle2 = require('./Lifecycle'); - -var _Lifecycle3 = _interopRequireDefault(_Lifecycle2); - -var _RouteContext2 = require('./RouteContext'); - -var _RouteContext3 = _interopRequireDefault(_RouteContext2); - -var _useRoutes2 = require('./useRoutes'); - -var _useRoutes3 = _interopRequireDefault(_useRoutes2); - -var _RouterContext2 = require('./RouterContext'); - -var _RouterContext3 = _interopRequireDefault(_RouterContext2); - -var _RoutingContext2 = require('./RoutingContext'); - -var _RoutingContext3 = _interopRequireDefault(_RoutingContext2); - -var _PropTypes3 = _interopRequireDefault(_PropTypes2); - -var _match2 = require('./match'); - -var _match3 = _interopRequireDefault(_match2); - -var _useRouterHistory2 = require('./useRouterHistory'); - -var _useRouterHistory3 = _interopRequireDefault(_useRouterHistory2); - -var _applyRouterMiddleware2 = require('./applyRouterMiddleware'); - -var _applyRouterMiddleware3 = _interopRequireDefault(_applyRouterMiddleware2); - -var _browserHistory2 = require('./browserHistory'); - -var _browserHistory3 = _interopRequireDefault(_browserHistory2); - -var _hashHistory2 = require('./hashHistory'); - -var _hashHistory3 = _interopRequireDefault(_hashHistory2); - -var _createMemoryHistory2 = require('./createMemoryHistory'); - -var _createMemoryHistory3 = _interopRequireDefault(_createMemoryHistory2); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.Router = _Router3.default; /* components */ - -exports.Link = _Link3.default; -exports.IndexLink = _IndexLink3.default; -exports.withRouter = _withRouter3.default; - -/* components (configuration) */ - -exports.IndexRedirect = _IndexRedirect3.default; -exports.IndexRoute = _IndexRoute3.default; -exports.Redirect = _Redirect3.default; -exports.Route = _Route3.default; - -/* mixins */ - -exports.History = _History3.default; -exports.Lifecycle = _Lifecycle3.default; -exports.RouteContext = _RouteContext3.default; - -/* utils */ - -exports.useRoutes = _useRoutes3.default; -exports.RouterContext = _RouterContext3.default; -exports.RoutingContext = _RoutingContext3.default; -exports.PropTypes = _PropTypes3.default; -exports.match = _match3.default; -exports.useRouterHistory = _useRouterHistory3.default; -exports.applyRouterMiddleware = _applyRouterMiddleware3.default; - -/* histories */ - -exports.browserHistory = _browserHistory3.default; -exports.hashHistory = _hashHistory3.default; -exports.createMemoryHistory = _createMemoryHistory3.default; -},{"./History":46,"./IndexLink":47,"./IndexRedirect":48,"./IndexRoute":49,"./Lifecycle":51,"./Link":52,"./PatternUtils":53,"./PropTypes":54,"./Redirect":55,"./Route":56,"./RouteContext":57,"./RouteUtils":58,"./Router":59,"./RouterContext":60,"./RoutingContext":62,"./applyRouterMiddleware":64,"./browserHistory":65,"./createMemoryHistory":67,"./hashHistory":73,"./match":76,"./useRouterHistory":79,"./useRoutes":80,"./withRouter":81}],"react":[function(require,module,exports){ +},{"./components/Provider":42,"./components/connect":43}],"react":[function(require,module,exports){ 'use strict'; module.exports = require('./lib/React'); -},{"./lib/React":107}],"redux-logger":[function(require,module,exports){ +},{"./lib/React":73}],"redux-logger":[function(require,module,exports){ "use strict"; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } @@ -55102,7 +51940,7 @@ exports.applyMiddleware = _applyMiddleware2["default"]; exports.compose = _compose2["default"]; }).call(this,require('_process')) -},{"./applyMiddleware":252,"./bindActionCreators":253,"./combineReducers":254,"./compose":255,"./createStore":256,"./utils/warning":257,"_process":37}],"shallowequal":[function(require,module,exports){ +},{"./applyMiddleware":218,"./bindActionCreators":219,"./combineReducers":220,"./compose":221,"./createStore":222,"./utils/warning":223,"_process":40}],"shallowequal":[function(require,module,exports){ 'use strict'; var fetchKeys = require('lodash.keys'); @@ -55151,7 +51989,7 @@ module.exports = function shallowEqual(objA, objB, compare, compareContext) { return true; }; -},{"lodash.keys":30}]},{},[]) +},{"lodash.keys":33}]},{},[]) //# sourceMappingURL=vendor.js.map diff --git a/netlib/http/authentication.py b/netlib/http/authentication.py index 38ea46d6..58fc9bdc 100644 --- a/netlib/http/authentication.py +++ b/netlib/http/authentication.py @@ -50,9 +50,9 @@ class NullProxyAuth(object): return {} -class BasicProxyAuth(NullProxyAuth): - CHALLENGE_HEADER = 'Proxy-Authenticate' - AUTH_HEADER = 'Proxy-Authorization' +class BasicAuth(NullProxyAuth): + CHALLENGE_HEADER = None + AUTH_HEADER = None def __init__(self, password_manager, realm): NullProxyAuth.__init__(self, password_manager) @@ -80,6 +80,16 @@ class BasicProxyAuth(NullProxyAuth): return {self.CHALLENGE_HEADER: 'Basic realm="%s"' % self.realm} +class BasicWebsiteAuth(BasicAuth): + CHALLENGE_HEADER = 'WWW-Authenticate' + AUTH_HEADER = 'Authorization' + + +class BasicProxyAuth(BasicAuth): + CHALLENGE_HEADER = 'Proxy-Authenticate' + AUTH_HEADER = 'Proxy-Authorization' + + class PassMan(object): def test(self, username_, password_token_): diff --git a/netlib/http/cookies.py b/netlib/http/cookies.py index dd0af99c..1421d8eb 100644 --- a/netlib/http/cookies.py +++ b/netlib/http/cookies.py @@ -26,6 +26,12 @@ variants. Serialization follows RFC6265. http://tools.ietf.org/html/rfc2965 """ +_cookie_params = set(( + 'expires', 'path', 'comment', 'max-age', + 'secure', 'httponly', 'version', +)) + + # TODO: Disallow LHS-only Cookie values @@ -263,27 +269,69 @@ def refresh_set_cookie_header(c, delta): return ret -def is_expired(cookie_attrs): +def get_expiration_ts(cookie_attrs): """ - Determines whether a cookie has expired. + Determines the time when the cookie will be expired. - Returns: boolean - """ + Considering both 'expires' and 'max-age' parameters. - # See if 'expires' time is in the past - expires = False + Returns: timestamp of when the cookie will expire. + None, if no expiration time is set. + """ if 'expires' in cookie_attrs: e = email.utils.parsedate_tz(cookie_attrs["expires"]) if e: - exp_ts = email.utils.mktime_tz(e) + return email.utils.mktime_tz(e) + + elif 'max-age' in cookie_attrs: + try: + max_age = int(cookie_attrs['Max-Age']) + except ValueError: + pass + else: now_ts = time.time() - expires = exp_ts < now_ts + return now_ts + max_age + + return None - # or if Max-Age is 0 - max_age = False - try: - max_age = int(cookie_attrs.get('Max-Age', 1)) == 0 - except ValueError: - pass - return expires or max_age +def is_expired(cookie_attrs): + """ + Determines whether a cookie has expired. + + Returns: boolean + """ + + exp_ts = get_expiration_ts(cookie_attrs) + now_ts = time.time() + + # If no expiration information was provided with the cookie + if exp_ts is None: + return False + else: + return exp_ts <= now_ts + + +def group_cookies(pairs): + """ + Converts a list of pairs to a (name, value, attrs) for each cookie. + """ + + if not pairs: + return [] + + cookie_list = [] + + # First pair is always a new cookie + name, value = pairs[0] + attrs = [] + + for k, v in pairs[1:]: + if k.lower() in _cookie_params: + attrs.append((k, v)) + else: + cookie_list.append((name, value, CookieAttrs(attrs))) + name, value, attrs = k, v, [] + + cookie_list.append((name, value, CookieAttrs(attrs))) + return cookie_list diff --git a/netlib/http/headers.py b/netlib/http/headers.py index 36e5060c..131e8ce5 100644 --- a/netlib/http/headers.py +++ b/netlib/http/headers.py @@ -158,7 +158,7 @@ class Headers(multidict.MultiDict): else: return super(Headers, self).items() - def replace(self, pattern, repl, flags=0): + def replace(self, pattern, repl, flags=0, count=0): """ Replaces a regular expression pattern with repl in each "name: value" header line. @@ -172,10 +172,10 @@ class Headers(multidict.MultiDict): repl = strutils.escaped_str_to_bytes(repl) pattern = re.compile(pattern, flags) replacements = 0 - + flag_count = count > 0 fields = [] for name, value in self.fields: - line, n = pattern.subn(repl, name + b": " + value) + line, n = pattern.subn(repl, name + b": " + value, count=count) try: name, value = line.split(b": ", 1) except ValueError: @@ -184,6 +184,10 @@ class Headers(multidict.MultiDict): pass else: replacements += n + if flag_count: + count -= n + if count == 0: + break fields.append((name, value)) self.fields = tuple(fields) return replacements diff --git a/netlib/http/message.py b/netlib/http/message.py index ce92bab1..0b64d4a6 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -260,7 +260,7 @@ class Message(basetypes.Serializable): if "content-encoding" not in self.headers: raise ValueError("Invalid content encoding {}".format(repr(e))) - def replace(self, pattern, repl, flags=0): + def replace(self, pattern, repl, flags=0, count=0): """ Replaces a regular expression pattern with repl in both the headers and the body of the message. Encoded body will be decoded @@ -276,9 +276,9 @@ class Message(basetypes.Serializable): replacements = 0 if self.content: self.content, replacements = re.subn( - pattern, repl, self.content, flags=flags + pattern, repl, self.content, flags=flags, count=count ) - replacements += self.headers.replace(pattern, repl, flags) + replacements += self.headers.replace(pattern, repl, flags=flags, count=count) return replacements # Legacy diff --git a/netlib/http/request.py b/netlib/http/request.py index d59fead4..e0aaa8a9 100644 --- a/netlib/http/request.py +++ b/netlib/http/request.py @@ -20,8 +20,20 @@ host_header_re = re.compile(r"^(?P<host>[^:]+|\[.+\])(?::(?P<port>\d+))?$") class RequestData(message.MessageData): - def __init__(self, first_line_format, method, scheme, host, port, path, http_version, headers=(), content=None, - timestamp_start=None, timestamp_end=None): + def __init__( + self, + first_line_format, + method, + scheme, + host, + port, + path, + http_version, + headers=(), + content=None, + timestamp_start=None, + timestamp_end=None + ): if isinstance(method, six.text_type): method = method.encode("ascii", "strict") if isinstance(scheme, six.text_type): @@ -68,7 +80,7 @@ class Request(message.Message): self.method, hostport, path ) - def replace(self, pattern, repl, flags=0): + def replace(self, pattern, repl, flags=0, count=0): """ Replaces a regular expression pattern with repl in the headers, the request path and the body of the request. Encoded content will be @@ -82,9 +94,9 @@ class Request(message.Message): if isinstance(repl, six.text_type): repl = strutils.escaped_str_to_bytes(repl) - c = super(Request, self).replace(pattern, repl, flags) + c = super(Request, self).replace(pattern, repl, flags, count) self.path, pc = re.subn( - pattern, repl, self.data.path, flags=flags + pattern, repl, self.data.path, flags=flags, count=count ) c += pc return c diff --git a/netlib/http/response.py b/netlib/http/response.py index 85f54940..ae29298f 100644 --- a/netlib/http/response.py +++ b/netlib/http/response.py @@ -1,19 +1,32 @@ from __future__ import absolute_import, print_function, division -from email.utils import parsedate_tz, formatdate, mktime_tz -import time import six - +import time +from email.utils import parsedate_tz, formatdate, mktime_tz +from netlib import human +from netlib import multidict from netlib.http import cookies from netlib.http import headers as nheaders from netlib.http import message -from netlib import multidict -from netlib import human +from netlib.http import status_codes +from typing import AnyStr # noqa +from typing import Dict # noqa +from typing import Iterable # noqa +from typing import Tuple # noqa +from typing import Union # noqa class ResponseData(message.MessageData): - def __init__(self, http_version, status_code, reason=None, headers=(), content=None, - timestamp_start=None, timestamp_end=None): + def __init__( + self, + http_version, + status_code, + reason=None, + headers=(), + content=None, + timestamp_start=None, + timestamp_end=None + ): if isinstance(http_version, six.text_type): http_version = http_version.encode("ascii", "strict") if isinstance(reason, six.text_type): @@ -54,6 +67,45 @@ class Response(message.Message): details=details ) + @classmethod + def make( + cls, + status_code=200, # type: int + content=b"", # type: AnyStr + headers=() # type: Union[Dict[AnyStr, AnyStr], Iterable[Tuple[bytes, bytes]]] + ): + """ + Simplified API for creating response objects. + """ + resp = cls( + b"HTTP/1.1", + status_code, + status_codes.RESPONSES.get(status_code, "").encode(), + (), + None + ) + # Assign this manually to update the content-length header. + if isinstance(content, bytes): + resp.content = content + elif isinstance(content, str): + resp.text = content + else: + raise TypeError("Expected content to be str or bytes, but is {}.".format( + type(content).__name__ + )) + + # Headers can be list or dict, we differentiate here. + if isinstance(headers, dict): + resp.headers = nheaders.Headers(**headers) + elif isinstance(headers, Iterable): + resp.headers = nheaders.Headers(headers) + else: + raise TypeError("Expected headers to be an iterable or dict, but is {}.".format( + type(headers).__name__ + )) + + return resp + @property def status_code(self): """ diff --git a/netlib/strutils.py b/netlib/strutils.py index 4a46b6b1..4cb3b805 100644 --- a/netlib/strutils.py +++ b/netlib/strutils.py @@ -121,6 +121,9 @@ def escaped_str_to_bytes(data): def is_mostly_bin(s): # type: (bytes) -> bool + if not s or len(s) == 0: + return False + return sum( i < 9 or 13 < i < 32 or 126 < i for i in six.iterbytes(s[:100]) diff --git a/netlib/websockets/__init__.py b/netlib/websockets/__init__.py index fea696d9..e14e8a7d 100644 --- a/netlib/websockets/__init__.py +++ b/netlib/websockets/__init__.py @@ -1,11 +1,37 @@ from __future__ import absolute_import, print_function, division -from .frame import FrameHeader, Frame, OPCODE -from .protocol import Masker, WebsocketsProtocol + +from .frame import FrameHeader +from .frame import Frame +from .frame import OPCODE +from .frame import CLOSE_REASON +from .masker import Masker +from .utils import MAGIC +from .utils import VERSION +from .utils import client_handshake_headers +from .utils import server_handshake_headers +from .utils import check_handshake +from .utils import check_client_version +from .utils import create_server_nonce +from .utils import get_extensions +from .utils import get_protocol +from .utils import get_client_key +from .utils import get_server_accept __all__ = [ "FrameHeader", "Frame", - "Masker", - "WebsocketsProtocol", "OPCODE", + "CLOSE_REASON", + "Masker", + "MAGIC", + "VERSION", + "client_handshake_headers", + "server_handshake_headers", + "check_handshake", + "check_client_version", + "create_server_nonce", + "get_extensions", + "get_protocol", + "get_client_key", + "get_server_accept", ] diff --git a/netlib/websockets/frame.py b/netlib/websockets/frame.py index 7d355699..e62d0e87 100644 --- a/netlib/websockets/frame.py +++ b/netlib/websockets/frame.py @@ -2,7 +2,6 @@ from __future__ import absolute_import import os import struct import io -import warnings import six @@ -10,7 +9,7 @@ from netlib import tcp from netlib import strutils from netlib import utils from netlib import human -from netlib.websockets import protocol +from .masker import Masker MAX_16_BIT_INT = (1 << 16) @@ -18,6 +17,7 @@ MAX_64_BIT_INT = (1 << 64) DEFAULT = object() +# RFC 6455, Section 5.2 - Base Framing Protocol OPCODE = utils.BiDi( CONTINUE=0x00, TEXT=0x01, @@ -27,6 +27,23 @@ OPCODE = utils.BiDi( PONG=0x0a ) +# RFC 6455, Section 7.4.1 - Defined Status Codes +CLOSE_REASON = utils.BiDi( + NORMAL_CLOSURE=1000, + GOING_AWAY=1001, + PROTOCOL_ERROR=1002, + UNSUPPORTED_DATA=1003, + RESERVED=1004, + RESERVED_NO_STATUS=1005, + RESERVED_ABNORMAL_CLOSURE=1006, + INVALID_PAYLOAD_DATA=1007, + POLICY_VIOLATION=1008, + MESSAGE_TOO_BIG=1009, + MANDATORY_EXTENSION=1010, + INTERNAL_ERROR=1011, + RESERVED_TLS_HANDHSAKE_FAILED=1015, +) + class FrameHeader(object): @@ -103,10 +120,6 @@ class FrameHeader(object): vals.append(" %s" % human.pretty_size(self.payload_length)) return "".join(vals) - def human_readable(self): - warnings.warn("FrameHeader.to_bytes is deprecated, use bytes(frame_header) instead.", DeprecationWarning) - return repr(self) - def __bytes__(self): first_byte = utils.setbit(0, 7, self.fin) first_byte = utils.setbit(first_byte, 6, self.rsv1) @@ -128,6 +141,9 @@ class FrameHeader(object): # '!Q' = pack as 64 bit unsigned long long # add 8 bytes extended payload length b += struct.pack('!Q', self.payload_length) + else: + raise ValueError("Payload length exceeds 64bit integer") + if self.masking_key: b += self.masking_key return b @@ -135,10 +151,6 @@ class FrameHeader(object): if six.PY2: __str__ = __bytes__ - def to_bytes(self): - warnings.warn("FrameHeader.to_bytes is deprecated, use bytes(frame_header) instead.", DeprecationWarning) - return bytes(self) - @classmethod def from_file(cls, fp): """ @@ -151,19 +163,17 @@ class FrameHeader(object): rsv1 = utils.getbit(first_byte, 6) rsv2 = utils.getbit(first_byte, 5) rsv3 = utils.getbit(first_byte, 4) - # grab right-most 4 bits - opcode = first_byte & 15 + opcode = first_byte & 0xF mask_bit = utils.getbit(second_byte, 7) - # grab the next 7 bits - length_code = second_byte & 127 + length_code = second_byte & 0x7F - # payload_lengthy > 125 indicates you need to read more bytes + # payload_length > 125 indicates you need to read more bytes # to get the actual payload length if length_code <= 125: payload_length = length_code elif length_code == 126: payload_length, = struct.unpack("!H", fp.safe_read(2)) - elif length_code == 127: + else: # length_code == 127: payload_length, = struct.unpack("!Q", fp.safe_read(8)) # masking key only present if mask bit set @@ -191,31 +201,30 @@ class FrameHeader(object): class Frame(object): - """ - Represents one websockets frame. - Constructor takes human readable forms of the frame components - from_bytes() is also avaliable. - - WebSockets Frame as defined in RFC6455 - - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ + Represents a single WebSockets frame. + Constructor takes human readable forms of the frame components. + from_bytes() reads from a file-like object to create a new Frame. + + WebSockets Frame as defined in RFC6455 + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ """ def __init__(self, payload=b"", **kwargs): @@ -224,27 +233,6 @@ class Frame(object): self.header = FrameHeader(**kwargs) @classmethod - def default(cls, message, from_client=False): - """ - Construct a basic websocket frame from some default values. - Creates a non-fragmented text frame. - """ - if from_client: - mask_bit = 1 - masking_key = os.urandom(4) - else: - mask_bit = 0 - masking_key = None - - return cls( - message, - fin=1, # final frame - opcode=OPCODE.TEXT, # text - mask=mask_bit, - masking_key=masking_key, - ) - - @classmethod def from_bytes(cls, bytestring): """ Construct a websocket frame from an in-memory bytestring @@ -258,17 +246,13 @@ class Frame(object): ret = ret + "\nPayload:\n" + strutils.bytes_to_escaped_str(self.payload) return ret - def human_readable(self): - warnings.warn("Frame.to_bytes is deprecated, use bytes(frame) instead.", DeprecationWarning) - return repr(self) - def __bytes__(self): """ Serialize the frame to wire format. Returns a string. """ b = bytes(self.header) if self.header.masking_key: - b += protocol.Masker(self.header.masking_key)(self.payload) + b += Masker(self.header.masking_key)(self.payload) else: b += self.payload return b @@ -276,15 +260,6 @@ class Frame(object): if six.PY2: __str__ = __bytes__ - def to_bytes(self): - warnings.warn("FrameHeader.to_bytes is deprecated, use bytes(frame_header) instead.", DeprecationWarning) - return bytes(self) - - def to_file(self, writer): - warnings.warn("Frame.to_file is deprecated, use wfile.write(bytes(frame)) instead.", DeprecationWarning) - writer.write(bytes(self)) - writer.flush() - @classmethod def from_file(cls, fp): """ @@ -297,20 +272,11 @@ class Frame(object): payload = fp.safe_read(header.payload_length) if header.mask == 1 and header.masking_key: - payload = protocol.Masker(header.masking_key)(payload) + payload = Masker(header.masking_key)(payload) - return cls( - payload, - fin=header.fin, - opcode=header.opcode, - mask=header.mask, - payload_length=header.payload_length, - masking_key=header.masking_key, - rsv1=header.rsv1, - rsv2=header.rsv2, - rsv3=header.rsv3, - length_code=header.length_code - ) + frame = cls(payload) + frame.header = header + return frame def __eq__(self, other): if isinstance(other, Frame): diff --git a/netlib/websockets/masker.py b/netlib/websockets/masker.py new file mode 100644 index 00000000..bd39ed6a --- /dev/null +++ b/netlib/websockets/masker.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import + +import six + + +class Masker(object): + """ + Data sent from the server must be masked to prevent malicious clients + from sending data over the wire in predictable patterns. + + Servers do not have to mask data they send to the client. + https://tools.ietf.org/html/rfc6455#section-5.3 + """ + + def __init__(self, key): + self.key = key + self.offset = 0 + + def mask(self, offset, data): + result = bytearray(data) + for i in range(len(data)): + if six.PY2: + result[i] ^= ord(self.key[offset % 4]) + else: + result[i] ^= self.key[offset % 4] + offset += 1 + result = bytes(result) + return result + + def __call__(self, data): + ret = self.mask(self.offset, data) + self.offset += len(ret) + return ret diff --git a/netlib/websockets/protocol.py b/netlib/websockets/protocol.py deleted file mode 100644 index af0eef7d..00000000 --- a/netlib/websockets/protocol.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Colleciton of utility functions that implement small portions of the RFC6455 -WebSockets Protocol Useful for building WebSocket clients and servers. - -Emphassis is on readabilty, simplicity and modularity, not performance or -completeness - -This is a work in progress and does not yet contain all the utilites need to -create fully complient client/servers # -Spec: https://tools.ietf.org/html/rfc6455 - -The magic sha that websocket servers must know to prove they understand -RFC6455 -""" - -from __future__ import absolute_import -import base64 -import hashlib -import os - -import six - -from netlib import http, strutils - -websockets_magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' -VERSION = "13" - - -class Masker(object): - - """ - Data sent from the server must be masked to prevent malicious clients - from sending data over the wire in predictable patterns - - Servers do not have to mask data they send to the client. - https://tools.ietf.org/html/rfc6455#section-5.3 - """ - - def __init__(self, key): - self.key = key - self.offset = 0 - - def mask(self, offset, data): - result = bytearray(data) - if six.PY2: - for i in range(len(data)): - result[i] ^= ord(self.key[offset % 4]) - offset += 1 - result = str(result) - else: - - for i in range(len(data)): - result[i] ^= self.key[offset % 4] - offset += 1 - result = bytes(result) - return result - - def __call__(self, data): - ret = self.mask(self.offset, data) - self.offset += len(ret) - return ret - - -class WebsocketsProtocol(object): - - def __init__(self): - pass - - @classmethod - def client_handshake_headers(self, key=None, version=VERSION): - """ - Create the headers for a valid HTTP upgrade request. If Key is not - specified, it is generated, and can be found in sec-websocket-key in - the returned header set. - - Returns an instance of http.Headers - """ - if not key: - key = base64.b64encode(os.urandom(16)).decode('ascii') - return http.Headers( - sec_websocket_key=key, - sec_websocket_version=version, - connection="Upgrade", - upgrade="websocket", - ) - - @classmethod - def server_handshake_headers(self, key): - """ - The server response is a valid HTTP 101 response. - """ - return http.Headers( - sec_websocket_accept=self.create_server_nonce(key), - connection="Upgrade", - upgrade="websocket" - ) - - @classmethod - def check_client_handshake(self, headers): - if headers.get("upgrade") != "websocket": - return - return headers.get("sec-websocket-key") - - @classmethod - def check_server_handshake(self, headers): - if headers.get("upgrade") != "websocket": - return - return headers.get("sec-websocket-accept") - - @classmethod - def create_server_nonce(self, client_nonce): - return base64.b64encode(hashlib.sha1(strutils.always_bytes(client_nonce) + websockets_magic).digest()) diff --git a/netlib/websockets/utils.py b/netlib/websockets/utils.py new file mode 100644 index 00000000..aa0d39a1 --- /dev/null +++ b/netlib/websockets/utils.py @@ -0,0 +1,90 @@ +""" +Collection of WebSockets Protocol utility functions (RFC6455) +Spec: https://tools.ietf.org/html/rfc6455 +""" + +from __future__ import absolute_import + +import base64 +import hashlib +import os + +from netlib import http, strutils + +MAGIC = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' +VERSION = "13" + + +def client_handshake_headers(version=None, key=None, protocol=None, extensions=None): + """ + Create the headers for a valid HTTP upgrade request. If Key is not + specified, it is generated, and can be found in sec-websocket-key in + the returned header set. + + Returns an instance of http.Headers + """ + if version is None: + version = VERSION + if key is None: + key = base64.b64encode(os.urandom(16)).decode('ascii') + h = http.Headers( + connection="upgrade", + upgrade="websocket", + sec_websocket_version=version, + sec_websocket_key=key, + ) + if protocol is not None: + h['sec-websocket-protocol'] = protocol + if extensions is not None: + h['sec-websocket-extensions'] = extensions + return h + + +def server_handshake_headers(client_key, protocol=None, extensions=None): + """ + The server response is a valid HTTP 101 response. + + Returns an instance of http.Headers + """ + h = http.Headers( + connection="upgrade", + upgrade="websocket", + sec_websocket_accept=create_server_nonce(client_key), + ) + if protocol is not None: + h['sec-websocket-protocol'] = protocol + if extensions is not None: + h['sec-websocket-extensions'] = extensions + return h + + +def check_handshake(headers): + return ( + "upgrade" in headers.get("connection", "").lower() and + headers.get("upgrade", "").lower() == "websocket" and + (headers.get("sec-websocket-key") is not None or headers.get("sec-websocket-accept") is not None) + ) + + +def create_server_nonce(client_nonce): + return base64.b64encode(hashlib.sha1(strutils.always_bytes(client_nonce) + MAGIC).digest()) + + +def check_client_version(headers): + return headers.get("sec-websocket-version", "") == VERSION + + +def get_extensions(headers): + return headers.get("sec-websocket-extensions", None) + + +def get_protocol(headers): + return headers.get("sec-websocket-protocol", None) + + +def get_client_key(headers): + return headers.get("sec-websocket-key", None) + + +def get_server_accept(headers): + return headers.get("sec-websocket-accept", None) diff --git a/pathod/language/http.py b/pathod/language/http.py index fdc5bba6..46027ca3 100644 --- a/pathod/language/http.py +++ b/pathod/language/http.py @@ -198,7 +198,7 @@ class Response(_HTTPMessage): 1, StatusCode(101) ) - headers = netlib.websockets.WebsocketsProtocol.server_handshake_headers( + headers = netlib.websockets.server_handshake_headers( settings.websocket_key ) for i in headers.fields: @@ -310,7 +310,7 @@ class Request(_HTTPMessage): 1, Method("get") ) - for i in netlib.websockets.WebsocketsProtocol.client_handshake_headers().fields: + for i in netlib.websockets.client_handshake_headers().fields: if not get_header(i[0], self.headers): tokens.append( Header( diff --git a/pathod/pathoc.py b/pathod/pathoc.py index 5831ba3e..a8923013 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -139,7 +139,7 @@ class WebsocketFrameReader(basethread.BaseThread): except exceptions.TcpDisconnect: return self.frames_queue.put(frm) - log("<< %s" % frm.header.human_readable()) + log("<< %s" % repr(frm.header)) if self.ws_read_limit is not None: self.ws_read_limit -= 1 starttime = time.time() diff --git a/pathod/pathod.py b/pathod/pathod.py index 7087cba6..bd0feb73 100644 --- a/pathod/pathod.py +++ b/pathod/pathod.py @@ -173,12 +173,13 @@ class PathodHandler(tcp.BaseHandler): retlog["cipher"] = self.get_current_cipher() m = utils.MemBool() - websocket_key = websockets.WebsocketsProtocol.check_client_handshake(headers) - self.settings.websocket_key = websocket_key + + valid_websockets_handshake = websockets.check_handshake(headers) + self.settings.websocket_key = websockets.get_client_key(headers) # If this is a websocket initiation, we respond with a proper # server response, unless over-ridden. - if websocket_key: + if valid_websockets_handshake: anchor_gen = language.parse_pathod("ws") else: anchor_gen = None @@ -225,7 +226,7 @@ class PathodHandler(tcp.BaseHandler): spec, lg ) - if nexthandler and websocket_key: + if nexthandler and valid_websockets_handshake: self.protocol = protocols.websockets.WebsocketsProtocol(self) return self.protocol.handle_websocket, retlog else: diff --git a/pathod/protocols/websockets.py b/pathod/protocols/websockets.py index a34e75e8..df83461a 100644 --- a/pathod/protocols/websockets.py +++ b/pathod/protocols/websockets.py @@ -20,7 +20,7 @@ class WebsocketsProtocol: lg("Error reading websocket frame: %s" % e) return None, None ended = time.time() - lg(frm.human_readable()) + lg(repr(frm)) retlog = dict( type="inbound", protocol="websockets", @@ -69,7 +69,7 @@ setup( "cryptography>=1.3, <1.5", "cssutils>=1.0.1, <1.1", "Flask>=0.10.1, <0.12", - "h2>=2.4.0, <3", + "h2>=2.4.1, <3", "html2text>=2016.1.8, <=2016.5.29", "hyperframe>=4.0.1, <5", "jsbeautifier>=1.6.3, <1.7", @@ -119,7 +119,6 @@ setup( ], 'examples': [ "beautifulsoup4>=4.4.1, <4.6", - "harparser>=0.2, <0.3", "pytz>=2015.07.0, <=2016.6.1", ] } diff --git a/test/mitmproxy/protocol/__init__.py b/test/mitmproxy/protocol/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/mitmproxy/protocol/__init__.py diff --git a/test/mitmproxy/test_protocol_http1.py b/test/mitmproxy/protocol/test_http1.py index cf7bd598..7d04c56b 100644 --- a/test/mitmproxy/test_protocol_http1.py +++ b/test/mitmproxy/protocol/test_http1.py @@ -1,7 +1,9 @@ +from __future__ import (absolute_import, print_function, division) + from netlib.http import http1 from netlib.tcp import TCPClient from netlib.tutils import treq -from . import tutils, tservers +from .. import tutils, tservers class TestHTTPFlow(object): diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/protocol/test_http2.py index f0fa9a40..1eabebf1 100644 --- a/test/mitmproxy/test_protocol_http2.py +++ b/test/mitmproxy/protocol/test_http2.py @@ -13,11 +13,11 @@ from mitmproxy import options from mitmproxy.proxy.config import ProxyConfig import netlib -from ..netlib import tservers as netlib_tservers +from ...netlib import tservers as netlib_tservers from netlib.exceptions import HttpException from netlib.http.http2 import framereader -from . import tservers +from .. import tservers import logging logging.getLogger("hyper.packages.hpack.hpack").setLevel(logging.WARNING) @@ -849,15 +849,15 @@ class TestMaxConcurrentStreams(_Http2Test): def test_max_concurrent_streams(self): client, h2_conn = self._setup_connection() new_streams = [1, 3, 5, 7, 9, 11] - for id in new_streams: + for stream_id in new_streams: # this will exceed MAX_CONCURRENT_STREAMS on the server connection # and cause mitmproxy to throttle stream creation to the server - self._send_request(client.wfile, h2_conn, stream_id=id, headers=[ + self._send_request(client.wfile, h2_conn, stream_id=stream_id, headers=[ (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), - ('X-Stream-ID', str(id)), + ('X-Stream-ID', str(stream_id)), ]) ended_streams = 0 diff --git a/test/mitmproxy/protocol/test_websockets.py b/test/mitmproxy/protocol/test_websockets.py new file mode 100644 index 00000000..e7e2684f --- /dev/null +++ b/test/mitmproxy/protocol/test_websockets.py @@ -0,0 +1,299 @@ +from __future__ import absolute_import, print_function, division + +import pytest +import os +import tempfile +import traceback + +from mitmproxy import options +from mitmproxy.proxy.config import ProxyConfig + +import netlib +from netlib import http +from ...netlib import tservers as netlib_tservers +from .. import tservers + +from netlib import websockets + + +class _WebSocketsServerBase(netlib_tservers.ServerTestBase): + + class handler(netlib.tcp.BaseHandler): + + def handle(self): + try: + request = http.http1.read_request(self.rfile) + assert websockets.check_handshake(request.headers) + + response = http.Response( + "HTTP/1.1", + 101, + reason=http.status_codes.RESPONSES.get(101), + headers=http.Headers( + connection='upgrade', + upgrade='websocket', + sec_websocket_accept=b'', + ), + content=b'', + ) + self.wfile.write(http.http1.assemble_response(response)) + self.wfile.flush() + + self.server.handle_websockets(self.rfile, self.wfile) + except: + traceback.print_exc() + + +class _WebSocketsTestBase(object): + + @classmethod + def setup_class(cls): + opts = cls.get_options() + cls.config = ProxyConfig(opts) + + tmaster = tservers.TestMaster(opts, cls.config) + tmaster.start_app(options.APP_HOST, options.APP_PORT) + cls.proxy = tservers.ProxyThread(tmaster) + cls.proxy.start() + + @classmethod + def teardown_class(cls): + cls.proxy.shutdown() + + @classmethod + def get_options(cls): + opts = options.Options( + listen_port=0, + no_upstream_cert=False, + ssl_insecure=True + ) + opts.cadir = os.path.join(tempfile.gettempdir(), "mitmproxy") + return opts + + @property + def master(self): + return self.proxy.tmaster + + def setup(self): + self.master.clear_log() + self.master.state.clear() + self.server.server.handle_websockets = self.handle_websockets + + def _setup_connection(self): + client = netlib.tcp.TCPClient(("127.0.0.1", self.proxy.port)) + client.connect() + + request = http.Request( + "authority", + "CONNECT", + "", + "localhost", + self.server.server.address.port, + "", + "HTTP/1.1", + content=b'') + client.wfile.write(http.http1.assemble_request(request)) + client.wfile.flush() + + response = http.http1.read_response(client.rfile, request) + + if self.ssl: + client.convert_to_ssl() + assert client.ssl_established + + request = http.Request( + "relative", + "GET", + "http", + "localhost", + self.server.server.address.port, + "/ws", + "HTTP/1.1", + headers=http.Headers( + connection="upgrade", + upgrade="websocket", + sec_websocket_version="13", + sec_websocket_key="1234", + ), + content=b'') + client.wfile.write(http.http1.assemble_request(request)) + client.wfile.flush() + + response = http.http1.read_response(client.rfile, request) + assert websockets.check_handshake(response.headers) + + return client + + +class _WebSocketsTest(_WebSocketsTestBase, _WebSocketsServerBase): + + @classmethod + def setup_class(cls): + _WebSocketsTestBase.setup_class() + _WebSocketsServerBase.setup_class(ssl=cls.ssl) + + @classmethod + def teardown_class(cls): + _WebSocketsTestBase.teardown_class() + _WebSocketsServerBase.teardown_class() + + +class TestSimple(_WebSocketsTest): + + @classmethod + def handle_websockets(cls, rfile, wfile): + wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'server-foobar'))) + wfile.flush() + + frame = websockets.Frame.from_file(rfile) + wfile.write(bytes(frame)) + wfile.flush() + + def test_simple(self): + client = self._setup_connection() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.payload == b'server-foobar' + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'client-foobar'))) + client.wfile.flush() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.payload == b'client-foobar' + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE))) + client.wfile.flush() + + +class TestSimpleTLS(_WebSocketsTest): + ssl = True + + @classmethod + def handle_websockets(cls, rfile, wfile): + wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'server-foobar'))) + wfile.flush() + + frame = websockets.Frame.from_file(rfile) + wfile.write(bytes(frame)) + wfile.flush() + + def test_simple_tls(self): + client = self._setup_connection() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.payload == b'server-foobar' + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'client-foobar'))) + client.wfile.flush() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.payload == b'client-foobar' + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE))) + client.wfile.flush() + + +class TestPing(_WebSocketsTest): + + @classmethod + def handle_websockets(cls, rfile, wfile): + wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PING, payload=b'foobar'))) + wfile.flush() + + frame = websockets.Frame.from_file(rfile) + assert frame.header.opcode == websockets.OPCODE.PONG + assert frame.payload == b'foobar' + + wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'pong-received'))) + wfile.flush() + + def test_ping(self): + client = self._setup_connection() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.header.opcode == websockets.OPCODE.PING + assert frame.payload == b'foobar' + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PONG, payload=frame.payload))) + client.wfile.flush() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.header.opcode == websockets.OPCODE.TEXT + assert frame.payload == b'pong-received' + + +class TestPong(_WebSocketsTest): + + @classmethod + def handle_websockets(cls, rfile, wfile): + frame = websockets.Frame.from_file(rfile) + assert frame.header.opcode == websockets.OPCODE.PING + assert frame.payload == b'foobar' + + wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PONG, payload=frame.payload))) + wfile.flush() + + def test_pong(self): + client = self._setup_connection() + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PING, payload=b'foobar'))) + client.wfile.flush() + + frame = websockets.Frame.from_file(client.rfile) + assert frame.header.opcode == websockets.OPCODE.PONG + assert frame.payload == b'foobar' + + +class TestClose(_WebSocketsTest): + + @classmethod + def handle_websockets(cls, rfile, wfile): + frame = websockets.Frame.from_file(rfile) + wfile.write(bytes(frame)) + wfile.flush() + + with pytest.raises(netlib.exceptions.TcpDisconnect): + websockets.Frame.from_file(rfile) + + def test_close(self): + client = self._setup_connection() + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE))) + client.wfile.flush() + + with pytest.raises(netlib.exceptions.TcpDisconnect): + websockets.Frame.from_file(client.rfile) + + def test_close_payload_1(self): + client = self._setup_connection() + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42'))) + client.wfile.flush() + + with pytest.raises(netlib.exceptions.TcpDisconnect): + websockets.Frame.from_file(client.rfile) + + def test_close_payload_2(self): + client = self._setup_connection() + + client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42foobar'))) + client.wfile.flush() + + with pytest.raises(netlib.exceptions.TcpDisconnect): + websockets.Frame.from_file(client.rfile) + + +class TestInvalidFrame(_WebSocketsTest): + + @classmethod + def handle_websockets(cls, rfile, wfile): + wfile.write(bytes(websockets.Frame(fin=1, opcode=15, payload=b'foobar'))) + wfile.flush() + + def test_invalid_frame(self): + client = self._setup_connection() + + # with pytest.raises(netlib.exceptions.TcpDisconnect): + frame = websockets.Frame.from_file(client.rfile) + assert frame.header.opcode == 15 + assert frame.payload == b'foobar' diff --git a/test/mitmproxy/test_examples.py b/test/mitmproxy/test_examples.py index 6c24ace5..83a37a36 100644 --- a/test/mitmproxy/test_examples.py +++ b/test/mitmproxy/test_examples.py @@ -1,16 +1,20 @@ import json +import os import six -import sys -import os.path -from mitmproxy.flow import master -from mitmproxy.flow import state + from mitmproxy import options from mitmproxy import contentviews from mitmproxy.builtins import script +from mitmproxy.flow import master +from mitmproxy.flow import state + import netlib.utils + from netlib import tutils as netutils from netlib.http import Headers +from netlib.http import cookies + from . import tutils, mastertest example_dir = netlib.utils.Data(__name__).push("../../examples") @@ -98,30 +102,66 @@ class TestScripts(mastertest.MasterTest): m.request(f) assert f.request.host == "mitmproxy.org" - def test_har_extractor(self): - if sys.version_info >= (3, 0): - with tutils.raises("does not work on Python 3"): - tscript("har_extractor.py") - return +class TestHARDump(): + + def flow(self, resp_content=b'message'): + times = dict( + timestamp_start=746203272, + timestamp_end=746203272, + ) + + # Create a dummy flow for testing + return tutils.tflow( + req=netutils.treq(method=b'GET', **times), + resp=netutils.tresp(content=resp_content, **times) + ) + + def test_no_file_arg(self): with tutils.raises(ScriptError): - tscript("har_extractor.py") + tscript("har_dump.py") + + def test_simple(self): + with tutils.tmpdir() as tdir: + path = os.path.join(tdir, "somefile") + + m, sc = tscript("har_dump.py", six.moves.shlex_quote(path)) + m.addons.invoke(m, "response", self.flow()) + m.addons.remove(sc) + with open(path, "r") as inp: + har = json.load(inp) + + assert len(har["log"]["entries"]) == 1 + + def test_base64(self): with tutils.tmpdir() as tdir: - times = dict( - timestamp_start=746203272, - timestamp_end=746203272, - ) - - path = os.path.join(tdir, "file") - m, sc = tscript("har_extractor.py", six.moves.shlex_quote(path)) - f = tutils.tflow( - req=netutils.treq(**times), - resp=netutils.tresp(**times) - ) - m.response(f) + path = os.path.join(tdir, "somefile") + + m, sc = tscript("har_dump.py", six.moves.shlex_quote(path)) + m.addons.invoke(m, "response", self.flow(resp_content=b"foo" + b"\xFF" * 10)) m.addons.remove(sc) - with open(path, "rb") as f: - test_data = json.load(f) - assert len(test_data["log"]["pages"]) == 1 + with open(path, "r") as inp: + har = json.load(inp) + + assert har["log"]["entries"][0]["response"]["content"]["encoding"] == "base64" + + def test_format_cookies(self): + m, sc = tscript("har_dump.py", "-") + format_cookies = sc.ns.ns["format_cookies"] + + CA = cookies.CookieAttrs + + f = format_cookies([("n", "v", CA([("k", "v")]))])[0] + assert f['name'] == "n" + assert f['value'] == "v" + assert not f['httpOnly'] + assert not f['secure'] + + f = format_cookies([("n", "v", CA([("httponly", None), ("secure", None)]))])[0] + assert f['httpOnly'] + assert f['secure'] + + f = format_cookies([("n", "v", CA([("expires", "Mon, 24-Aug-2037 00:00:00 GMT")]))])[0] + assert f['expires'] diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 84838018..f7c64e50 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -85,22 +85,22 @@ class TestProcessProxyOptions: @mock.patch("mitmproxy.platform.resolver") def test_modes(self, _): - # self.assert_noerr("-R", "http://localhost") - # self.assert_err("expected one argument", "-R") - # self.assert_err("Invalid server specification", "-R", "reverse") - # - # self.assert_noerr("-T") - # - # self.assert_noerr("-U", "http://localhost") - # self.assert_err("expected one argument", "-U") - # self.assert_err("Invalid server specification", "-U", "upstream") - # - # self.assert_noerr("--upstream-auth", "test:test") - # self.assert_err("expected one argument", "--upstream-auth") + self.assert_noerr("-R", "http://localhost") + self.assert_err("expected one argument", "-R") + self.assert_err("Invalid server specification", "-R", "reverse") + + self.assert_noerr("-T") + + self.assert_noerr("-U", "http://localhost") + self.assert_err("expected one argument", "-U") + self.assert_err("Invalid server specification", "-U", "upstream") + + self.assert_noerr("--upstream-auth", "test:test") + self.assert_err("expected one argument", "--upstream-auth") self.assert_err( "Invalid upstream auth specification", "--upstream-auth", "test" ) - # self.assert_err("mutually exclusive", "-R", "http://localhost", "-T") + self.assert_err("mutually exclusive", "-R", "http://localhost", "-T") def test_socks_auth(self): self.assert_err( diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py index 78e9b5c7..e0a8da47 100644 --- a/test/mitmproxy/test_server.py +++ b/test/mitmproxy/test_server.py @@ -313,6 +313,22 @@ class TestHTTPAuth(tservers.HTTPProxyTest): assert ret.status_code == 202 +class TestHTTPReverseAuth(tservers.ReverseProxyTest): + def test_auth(self): + self.master.options.auth_singleuser = "test:test" + assert self.pathod("202").status_code == 401 + p = self.pathoc() + ret = p.request(""" + get + '/p/202' + h'%s'='%s' + """ % ( + http.authentication.BasicWebsiteAuth.AUTH_HEADER, + authentication.assemble_http_basic_auth("basic", "test", "test") + )) + assert ret.status_code == 202 + + class TestHTTPS(tservers.HTTPProxyTest, CommonMixin, TcpMixin): ssl = True ssloptions = pathod.SSLOptions(request_client_cert=True) @@ -456,6 +472,11 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin): reverse = True +class TestReverseSSL(tservers.ReverseProxyTest, CommonMixin, TcpMixin): + reverse = True + ssl = True + + class TestSocks5(tservers.SocksModeTest): def test_simple(self): diff --git a/test/netlib/http/test_cookies.py b/test/netlib/http/test_cookies.py index 17e21b94..efd8ba80 100644 --- a/test/netlib/http/test_cookies.py +++ b/test/netlib/http/test_cookies.py @@ -1,6 +1,10 @@ +import time + from netlib.http import cookies from netlib.tutils import raises +import mock + def test_read_token(): tokens = [ @@ -247,6 +251,22 @@ def test_refresh_cookie(): assert cookies.refresh_set_cookie_header(c, 0) +@mock.patch('time.time') +def test_get_expiration_ts(*args): + # Freeze time + now_ts = 17 + time.time.return_value = now_ts + + CA = cookies.CookieAttrs + F = cookies.get_expiration_ts + + assert F(CA([("Expires", "Thu, 01-Jan-1970 00:00:00 GMT")])) == 0 + assert F(CA([("Expires", "Mon, 24-Aug-2037 00:00:00 GMT")])) == 2134684800 + + assert F(CA([("Max-Age", "0")])) == now_ts + assert F(CA([("Max-Age", "31")])) == now_ts + 31 + + def test_is_expired(): CA = cookies.CookieAttrs @@ -260,9 +280,53 @@ def test_is_expired(): # or both assert cookies.is_expired(CA([("Expires", "Thu, 01-Jan-1970 00:00:00 GMT"), ("Max-Age", "0")])) - assert not cookies.is_expired(CA([("Expires", "Thu, 24-Aug-2063 00:00:00 GMT")])) + assert not cookies.is_expired(CA([("Expires", "Mon, 24-Aug-2037 00:00:00 GMT")])) assert not cookies.is_expired(CA([("Max-Age", "1")])) - assert not cookies.is_expired(CA([("Expires", "Thu, 15-Jul-2068 00:00:00 GMT"), ("Max-Age", "1")])) + assert not cookies.is_expired(CA([("Expires", "Wed, 15-Jul-2037 00:00:00 GMT"), ("Max-Age", "1")])) assert not cookies.is_expired(CA([("Max-Age", "nan")])) assert not cookies.is_expired(CA([("Expires", "false")])) + + +def test_group_cookies(): + CA = cookies.CookieAttrs + groups = [ + [ + "one=uno; foo=bar; foo=baz", + [ + ('one', 'uno', CA([])), + ('foo', 'bar', CA([])), + ('foo', 'baz', CA([])) + ] + ], + [ + "one=uno; Path=/; foo=bar; Max-Age=0; foo=baz; expires=24-08-1993", + [ + ('one', 'uno', CA([('Path', '/')])), + ('foo', 'bar', CA([('Max-Age', '0')])), + ('foo', 'baz', CA([('expires', '24-08-1993')])) + ] + ], + [ + "one=uno;", + [ + ('one', 'uno', CA([])) + ] + ], + [ + "one=uno; Path=/; Max-Age=0; Expires=24-08-1993", + [ + ('one', 'uno', CA([('Path', '/'), ('Max-Age', '0'), ('Expires', '24-08-1993')])) + ] + ], + [ + "path=val; Path=/", + [ + ('path', 'val', CA([('Path', '/')])) + ] + ] + ] + + for c, expected in groups: + observed = cookies.group_cookies(cookies.parse_cookie_header(c)) + assert observed == expected diff --git a/test/netlib/http/test_headers.py b/test/netlib/http/test_headers.py index 51537310..ad2bc548 100644 --- a/test/netlib/http/test_headers.py +++ b/test/netlib/http/test_headers.py @@ -75,6 +75,11 @@ class TestHeaders(object): assert replacements == 0 assert headers["Host"] == "example.com" + def test_replace_with_count(self): + headers = Headers(Host="foobarfoo.com", Accept="foo/bar") + replacements = headers.replace("foo", "bar", count=1) + assert replacements == 1 + def test_parse_content_type(): p = parse_content_type diff --git a/test/netlib/http/test_message.py b/test/netlib/http/test_message.py index 12e4706c..74272309 100644 --- a/test/netlib/http/test_message.py +++ b/test/netlib/http/test_message.py @@ -99,6 +99,16 @@ class TestMessage(object): def test_http_version(self): _test_decoded_attr(tresp(), "http_version") + def test_replace(self): + r = tresp() + r.content = b"foofootoo" + r.replace(b"foo", "gg") + assert r.content == b"ggggtoo" + + r.content = b"foofootoo" + r.replace(b"foo", "gg", count=1) + assert r.content == b"ggfootoo" + class TestMessageContentEncoding(object): def test_simple(self): diff --git a/test/netlib/http/test_request.py b/test/netlib/http/test_request.py index f3cd8b71..9baabaa6 100644 --- a/test/netlib/http/test_request.py +++ b/test/netlib/http/test_request.py @@ -26,6 +26,16 @@ class TestRequestCore(object): request.host = None assert repr(request) == "Request(GET /path)" + def replace(self): + r = treq() + r.path = b"foobarfoo" + r.replace(b"foo", "bar") + assert r.path == b"barbarbar" + + r.path = b"foobarfoo" + r.replace(b"foo", "bar", count=1) + assert r.path == b"barbarfoo" + def test_first_line_format(self): _test_passthrough_attr(treq(), "first_line_format") diff --git a/test/netlib/http/test_response.py b/test/netlib/http/test_response.py index b3c2f736..c7b1b646 100644 --- a/test/netlib/http/test_response.py +++ b/test/netlib/http/test_response.py @@ -5,6 +5,7 @@ import email import time from netlib.http import Headers +from netlib.http import Response from netlib.http.cookies import CookieAttrs from netlib.tutils import raises, tresp from .test_message import _test_passthrough_attr, _test_decoded_attr @@ -28,6 +29,25 @@ class TestResponseCore(object): response.content = None assert repr(response) == "Response(200 OK, no content)" + def test_make(self): + r = Response.make() + assert r.status_code == 200 + assert r.content == b"" + + Response.make(content=b"foo") + Response.make(content="foo") + with raises(TypeError): + Response.make(content=42) + + r = Response.make(headers=[(b"foo", b"bar")]) + assert r.headers["foo"] == "bar" + + r = Response.make(headers=({"foo": "baz"})) + assert r.headers["foo"] == "baz" + + with raises(TypeError): + Response.make(headers=42) + def test_status_code(self): _test_passthrough_attr(tresp(), "status_code") diff --git a/test/netlib/test_strutils.py b/test/netlib/test_strutils.py index 52299e59..5be254a3 100644 --- a/test/netlib/test_strutils.py +++ b/test/netlib/test_strutils.py @@ -85,6 +85,7 @@ def test_escaped_str_to_bytes(): def test_is_mostly_bin(): assert not strutils.is_mostly_bin(b"foo\xFF") assert strutils.is_mostly_bin(b"foo" + b"\xFF" * 10) + assert not strutils.is_mostly_bin("") def test_is_xml(): diff --git a/test/netlib/tservers.py b/test/netlib/tservers.py index 666f97ac..a80dcb28 100644 --- a/test/netlib/tservers.py +++ b/test/netlib/tservers.py @@ -100,7 +100,8 @@ class ServerTestBase(object): @classmethod def makeserver(cls, **kwargs): - return _TServer(cls.ssl, cls.q, cls.handler, cls.addr, **kwargs) + ssl = kwargs.pop('ssl', cls.ssl) + return _TServer(ssl, cls.q, cls.handler, cls.addr, **kwargs) @classmethod def teardown_class(cls): diff --git a/test/netlib/websockets/test_frame.py b/test/netlib/websockets/test_frame.py new file mode 100644 index 00000000..cce39454 --- /dev/null +++ b/test/netlib/websockets/test_frame.py @@ -0,0 +1,164 @@ +import os +import codecs +import pytest + +from netlib import websockets +from netlib import tutils + + +class TestFrameHeader(object): + + @pytest.mark.parametrize("input,expected", [ + (0, '0100'), + (125, '017D'), + (126, '017E007E'), + (127, '017E007F'), + (142, '017E008E'), + (65534, '017EFFFE'), + (65535, '017EFFFF'), + (65536, '017F0000000000010000'), + (8589934591, '017F00000001FFFFFFFF'), + (2 ** 64 - 1, '017FFFFFFFFFFFFFFFFF'), + ]) + def test_serialization_length(self, input, expected): + h = websockets.FrameHeader( + opcode=websockets.OPCODE.TEXT, + payload_length=input, + ) + assert bytes(h) == codecs.decode(expected, 'hex') + + def test_serialization_too_large(self): + h = websockets.FrameHeader( + payload_length=2 ** 64 + 1, + ) + with pytest.raises(ValueError): + bytes(h) + + @pytest.mark.parametrize("input,expected", [ + ('0100', 0), + ('017D', 125), + ('017E007E', 126), + ('017E007F', 127), + ('017E008E', 142), + ('017EFFFE', 65534), + ('017EFFFF', 65535), + ('017F0000000000010000', 65536), + ('017F00000001FFFFFFFF', 8589934591), + ('017FFFFFFFFFFFFFFFFF', 2 ** 64 - 1), + ]) + def test_deserialization_length(self, input, expected): + h = websockets.FrameHeader.from_file(tutils.treader(codecs.decode(input, 'hex'))) + assert h.payload_length == expected + + @pytest.mark.parametrize("input,expected", [ + ('0100', (False, None)), + ('018000000000', (True, '00000000')), + ('018012345678', (True, '12345678')), + ]) + def test_deserialization_masking(self, input, expected): + h = websockets.FrameHeader.from_file(tutils.treader(codecs.decode(input, 'hex'))) + assert h.mask == expected[0] + if h.mask: + assert h.masking_key == codecs.decode(expected[1], 'hex') + + def test_equality(self): + h = websockets.FrameHeader(mask=True, masking_key=b'1234') + h2 = websockets.FrameHeader(mask=True, masking_key=b'1234') + assert h == h2 + + h = websockets.FrameHeader(fin=True) + h2 = websockets.FrameHeader(fin=False) + assert h != h2 + + assert h != 'foobar' + + def test_roundtrip(self): + def round(*args, **kwargs): + h = websockets.FrameHeader(*args, **kwargs) + h2 = websockets.FrameHeader.from_file(tutils.treader(bytes(h))) + assert h == h2 + + round() + round(fin=True) + round(rsv1=True) + round(rsv2=True) + round(rsv3=True) + round(payload_length=1) + round(payload_length=100) + round(payload_length=1000) + round(payload_length=10000) + round(opcode=websockets.OPCODE.PING) + round(masking_key=b"test") + + def test_human_readable(self): + f = websockets.FrameHeader( + masking_key=b"test", + fin=True, + payload_length=10 + ) + assert repr(f) + + f = websockets.FrameHeader() + assert repr(f) + + def test_funky(self): + f = websockets.FrameHeader(masking_key=b"test", mask=False) + raw = bytes(f) + f2 = websockets.FrameHeader.from_file(tutils.treader(raw)) + assert not f2.mask + + def test_violations(self): + tutils.raises("opcode", websockets.FrameHeader, opcode=17) + tutils.raises("masking key", websockets.FrameHeader, masking_key=b"x") + + def test_automask(self): + f = websockets.FrameHeader(mask=True) + assert f.masking_key + + f = websockets.FrameHeader(masking_key=b"foob") + assert f.mask + + f = websockets.FrameHeader(masking_key=b"foob", mask=0) + assert not f.mask + assert f.masking_key + + +class TestFrame(object): + def test_equality(self): + f = websockets.Frame(payload=b'1234') + f2 = websockets.Frame(payload=b'1234') + assert f == f2 + + assert f != b'1234' + + def test_roundtrip(self): + def round(*args, **kwargs): + f = websockets.Frame(*args, **kwargs) + raw = bytes(f) + f2 = websockets.Frame.from_file(tutils.treader(raw)) + assert f == f2 + round(b"test") + round(b"test", fin=1) + round(b"test", rsv1=1) + round(b"test", opcode=websockets.OPCODE.PING) + round(b"test", masking_key=b"test") + + def test_human_readable(self): + f = websockets.Frame() + assert repr(f) + + f = websockets.Frame(b"foobar") + assert "foobar" in repr(f) + + @pytest.mark.parametrize("masked", [True, False]) + @pytest.mark.parametrize("length", [100, 50000, 150000]) + def test_serialization_bijection(self, masked, length): + frame = websockets.Frame( + os.urandom(length), + fin=True, + opcode=websockets.OPCODE.TEXT, + mask=int(masked), + masking_key=(os.urandom(4) if masked else None) + ) + serialized = bytes(frame) + assert frame == websockets.Frame.from_bytes(serialized) diff --git a/test/netlib/websockets/test_masker.py b/test/netlib/websockets/test_masker.py new file mode 100644 index 00000000..528fce71 --- /dev/null +++ b/test/netlib/websockets/test_masker.py @@ -0,0 +1,23 @@ +import codecs +import pytest + +from netlib import websockets + + +class TestMasker(object): + + @pytest.mark.parametrize("input,expected", [ + ([b"a"], '00'), + ([b"four"], '070d1616'), + ([b"fourf"], '070d161607'), + ([b"fourfive"], '070d1616070b1501'), + ([b"a", b"aasdfasdfa", b"asdf"], '000302170504021705040205120605'), + ([b"a" * 50, b"aasdfasdfa", b"asdf"], '00030205000302050003020500030205000302050003020500030205000302050003020500030205000302050003020500030205120605051206050500110702'), # noqa + ]) + def test_masker(self, input, expected): + m = websockets.Masker(b"abcd") + data = b"".join([m(t) for t in input]) + assert data == codecs.decode(expected, 'hex') + + data = websockets.Masker(b"abcd")(data) + assert data == b"".join(input) diff --git a/test/netlib/websockets/test_utils.py b/test/netlib/websockets/test_utils.py new file mode 100644 index 00000000..34765e04 --- /dev/null +++ b/test/netlib/websockets/test_utils.py @@ -0,0 +1,105 @@ +import pytest + +from netlib import http +from netlib import websockets + + +class TestUtils(object): + + def test_client_handshake_headers(self): + h = websockets.client_handshake_headers(version='42') + assert h['sec-websocket-version'] == '42' + + h = websockets.client_handshake_headers(key='some-key') + assert h['sec-websocket-key'] == 'some-key' + + h = websockets.client_handshake_headers(protocol='foobar') + assert h['sec-websocket-protocol'] == 'foobar' + + h = websockets.client_handshake_headers(extensions='foo; bar') + assert h['sec-websocket-extensions'] == 'foo; bar' + + def test_server_handshake_headers(self): + h = websockets.server_handshake_headers('some-key') + assert h['sec-websocket-accept'] == '8iILEZtcVdtFD7MDlPKip9ec9nw=' + assert 'sec-websocket-protocol' not in h + assert 'sec-websocket-extensions' not in h + + h = websockets.server_handshake_headers('some-key', 'foobar', 'foo; bar') + assert h['sec-websocket-accept'] == '8iILEZtcVdtFD7MDlPKip9ec9nw=' + assert h['sec-websocket-protocol'] == 'foobar' + assert h['sec-websocket-extensions'] == 'foo; bar' + + @pytest.mark.parametrize("input,expected", [ + ([(b'connection', b'upgrade'), (b'upgrade', b'websocket'), (b'sec-websocket-key', b'foobar')], True), + ([(b'connection', b'upgrade'), (b'upgrade', b'websocket'), (b'sec-websocket-accept', b'foobar')], True), + ([(b'Connection', b'UpgRaDe'), (b'Upgrade', b'WebSocKeT'), (b'Sec-WebSockeT-KeY', b'foobar')], True), + ([(b'Connection', b'UpgRaDe'), (b'Upgrade', b'WebSocKeT'), (b'Sec-WebSockeT-AccePt', b'foobar')], True), + ([(b'connection', b'foo'), (b'upgrade', b'bar'), (b'sec-websocket-key', b'foobar')], False), + ([(b'connection', b'upgrade'), (b'upgrade', b'websocket')], False), + ([(b'connection', b'upgrade'), (b'sec-websocket-key', b'foobar')], False), + ([(b'upgrade', b'websocket'), (b'sec-websocket-key', b'foobar')], False), + ([], False), + ]) + def test_check_handshake(self, input, expected): + h = http.Headers(input) + assert websockets.check_handshake(h) == expected + + @pytest.mark.parametrize("input,expected", [ + ([(b'sec-websocket-version', b'13')], True), + ([(b'Sec-WebSockeT-VerSion', b'13')], True), + ([(b'sec-websocket-version', b'9')], False), + ([(b'sec-websocket-version', b'42')], False), + ([(b'sec-websocket-version', b'')], False), + ([], False), + ]) + def test_check_client_version(self, input, expected): + h = http.Headers(input) + assert websockets.check_client_version(h) == expected + + @pytest.mark.parametrize("input,expected", [ + ('foobar', b'AzhRPA4TNwR6I/riJheN0TfR7+I='), + (b'foobar', b'AzhRPA4TNwR6I/riJheN0TfR7+I='), + ]) + def test_create_server_nonce(self, input, expected): + assert websockets.create_server_nonce(input) == expected + + @pytest.mark.parametrize("input,expected", [ + ([(b'sec-websocket-extensions', b'foo; bar')], 'foo; bar'), + ([(b'Sec-WebSockeT-ExteNsionS', b'foo; bar')], 'foo; bar'), + ([(b'sec-websocket-extensions', b'')], ''), + ([], None), + ]) + def test_get_extensions(self, input, expected): + h = http.Headers(input) + assert websockets.get_extensions(h) == expected + + @pytest.mark.parametrize("input,expected", [ + ([(b'sec-websocket-protocol', b'foobar')], 'foobar'), + ([(b'Sec-WebSockeT-ProTocoL', b'foobar')], 'foobar'), + ([(b'sec-websocket-protocol', b'')], ''), + ([], None), + ]) + def test_get_protocol(self, input, expected): + h = http.Headers(input) + assert websockets.get_protocol(h) == expected + + @pytest.mark.parametrize("input,expected", [ + ([(b'sec-websocket-key', b'foobar')], 'foobar'), + ([(b'Sec-WebSockeT-KeY', b'foobar')], 'foobar'), + ([(b'sec-websocket-key', b'')], ''), + ([], None), + ]) + def test_get_client_key(self, input, expected): + h = http.Headers(input) + assert websockets.get_client_key(h) == expected + + @pytest.mark.parametrize("input,expected", [ + ([(b'sec-websocket-accept', b'foobar')], 'foobar'), + ([(b'Sec-WebSockeT-AccepT', b'foobar')], 'foobar'), + ([(b'sec-websocket-accept', b'')], ''), + ([], None), + ]) + def test_get_server_accept(self, input, expected): + h = http.Headers(input) + assert websockets.get_server_accept(h) == expected diff --git a/test/netlib/websockets/test_websockets.py b/test/netlib/websockets/test_websockets.py deleted file mode 100644 index 50fa26e6..00000000 --- a/test/netlib/websockets/test_websockets.py +++ /dev/null @@ -1,269 +0,0 @@ -import os - -from netlib.http.http1 import read_response, read_request - -from netlib import tcp -from netlib import tutils -from netlib import websockets -from netlib.http import status_codes -from netlib.tutils import treq -from netlib import exceptions - -from .. import tservers - - -class WebSocketsEchoHandler(tcp.BaseHandler): - - def __init__(self, connection, address, server): - super(WebSocketsEchoHandler, self).__init__( - connection, address, server - ) - self.protocol = websockets.WebsocketsProtocol() - self.handshake_done = False - - def handle(self): - while True: - if not self.handshake_done: - self.handshake() - else: - self.read_next_message() - - def read_next_message(self): - frame = websockets.Frame.from_file(self.rfile) - self.on_message(frame.payload) - - def send_message(self, message): - frame = websockets.Frame.default(message, from_client=False) - frame.to_file(self.wfile) - - def handshake(self): - - req = read_request(self.rfile) - key = self.protocol.check_client_handshake(req.headers) - - preamble = 'HTTP/1.1 101 %s' % status_codes.RESPONSES.get(101) - self.wfile.write(preamble.encode() + b"\r\n") - headers = self.protocol.server_handshake_headers(key) - self.wfile.write(str(headers) + "\r\n") - self.wfile.flush() - self.handshake_done = True - - def on_message(self, message): - if message is not None: - self.send_message(message) - - -class WebSocketsClient(tcp.TCPClient): - - def __init__(self, address, source_address=None): - super(WebSocketsClient, self).__init__(address, source_address) - self.protocol = websockets.WebsocketsProtocol() - self.client_nonce = None - - def connect(self): - super(WebSocketsClient, self).connect() - - preamble = b'GET / HTTP/1.1' - self.wfile.write(preamble + b"\r\n") - headers = self.protocol.client_handshake_headers() - self.client_nonce = headers["sec-websocket-key"].encode("ascii") - self.wfile.write(bytes(headers) + b"\r\n") - self.wfile.flush() - - resp = read_response(self.rfile, treq(method=b"GET")) - server_nonce = self.protocol.check_server_handshake(resp.headers) - - if not server_nonce == self.protocol.create_server_nonce(self.client_nonce): - self.close() - - def read_next_message(self): - return websockets.Frame.from_file(self.rfile).payload - - def send_message(self, message): - frame = websockets.Frame.default(message, from_client=True) - frame.to_file(self.wfile) - - -class TestWebSockets(tservers.ServerTestBase): - handler = WebSocketsEchoHandler - - def __init__(self): - self.protocol = websockets.WebsocketsProtocol() - - def random_bytes(self, n=100): - return os.urandom(n) - - def echo(self, msg): - client = WebSocketsClient(("127.0.0.1", self.port)) - client.connect() - client.send_message(msg) - response = client.read_next_message() - assert response == msg - - def test_simple_echo(self): - self.echo(b"hello I'm the client") - - def test_frame_sizes(self): - # length can fit in the the 7 bit payload length - small_msg = self.random_bytes(100) - # 50kb, sligthly larger than can fit in a 7 bit int - medium_msg = self.random_bytes(50000) - # 150kb, slightly larger than can fit in a 16 bit int - large_msg = self.random_bytes(150000) - - self.echo(small_msg) - self.echo(medium_msg) - self.echo(large_msg) - - def test_default_builder(self): - """ - default builder should always generate valid frames - """ - msg = self.random_bytes() - assert websockets.Frame.default(msg, from_client=True) - assert websockets.Frame.default(msg, from_client=False) - - def test_serialization_bijection(self): - """ - Ensure that various frame types can be serialized/deserialized back - and forth between to_bytes() and from_bytes() - """ - for is_client in [True, False]: - for num_bytes in [100, 50000, 150000]: - frame = websockets.Frame.default( - self.random_bytes(num_bytes), is_client - ) - frame2 = websockets.Frame.from_bytes( - frame.to_bytes() - ) - assert frame == frame2 - - bytes = b'\x81\x03cba' - assert websockets.Frame.from_bytes(bytes).to_bytes() == bytes - - def test_check_server_handshake(self): - headers = self.protocol.server_handshake_headers("key") - assert self.protocol.check_server_handshake(headers) - headers["Upgrade"] = "not_websocket" - assert not self.protocol.check_server_handshake(headers) - - def test_check_client_handshake(self): - headers = self.protocol.client_handshake_headers("key") - assert self.protocol.check_client_handshake(headers) == "key" - headers["Upgrade"] = "not_websocket" - assert not self.protocol.check_client_handshake(headers) - - -class BadHandshakeHandler(WebSocketsEchoHandler): - - def handshake(self): - - client_hs = read_request(self.rfile) - self.protocol.check_client_handshake(client_hs.headers) - - preamble = 'HTTP/1.1 101 %s\r\n' % status_codes.RESPONSES.get(101) - self.wfile.write(preamble.encode()) - headers = self.protocol.server_handshake_headers(b"malformed key") - self.wfile.write(bytes(headers) + b"\r\n") - self.wfile.flush() - self.handshake_done = True - - -class TestBadHandshake(tservers.ServerTestBase): - - """ - Ensure that the client disconnects if the server handshake is malformed - """ - handler = BadHandshakeHandler - - def test(self): - with tutils.raises(exceptions.TcpDisconnect): - client = WebSocketsClient(("127.0.0.1", self.port)) - client.connect() - client.send_message(b"hello") - - -class TestFrameHeader: - - def test_roundtrip(self): - def round(*args, **kwargs): - f = websockets.FrameHeader(*args, **kwargs) - f2 = websockets.FrameHeader.from_file(tutils.treader(bytes(f))) - assert f == f2 - round() - round(fin=1) - round(rsv1=1) - round(rsv2=1) - round(rsv3=1) - round(payload_length=1) - round(payload_length=100) - round(payload_length=1000) - round(payload_length=10000) - round(opcode=websockets.OPCODE.PING) - round(masking_key=b"test") - - def test_human_readable(self): - f = websockets.FrameHeader( - masking_key=b"test", - fin=True, - payload_length=10 - ) - assert repr(f) - f = websockets.FrameHeader() - assert repr(f) - - def test_funky(self): - f = websockets.FrameHeader(masking_key=b"test", mask=False) - raw = bytes(f) - f2 = websockets.FrameHeader.from_file(tutils.treader(raw)) - assert not f2.mask - - def test_violations(self): - tutils.raises("opcode", websockets.FrameHeader, opcode=17) - tutils.raises("masking key", websockets.FrameHeader, masking_key=b"x") - - def test_automask(self): - f = websockets.FrameHeader(mask=True) - assert f.masking_key - - f = websockets.FrameHeader(masking_key=b"foob") - assert f.mask - - f = websockets.FrameHeader(masking_key=b"foob", mask=0) - assert not f.mask - assert f.masking_key - - -class TestFrame: - - def test_roundtrip(self): - def round(*args, **kwargs): - f = websockets.Frame(*args, **kwargs) - raw = bytes(f) - f2 = websockets.Frame.from_file(tutils.treader(raw)) - assert f == f2 - round(b"test") - round(b"test", fin=1) - round(b"test", rsv1=1) - round(b"test", opcode=websockets.OPCODE.PING) - round(b"test", masking_key=b"test") - - def test_human_readable(self): - f = websockets.Frame() - assert repr(f) - - -def test_masker(): - tests = [ - [b"a"], - [b"four"], - [b"fourf"], - [b"fourfive"], - [b"a", b"aasdfasdfa", b"asdf"], - [b"a" * 50, b"aasdfasdfa", b"asdf"], - ] - for i in tests: - m = websockets.Masker(b"abcd") - data = b"".join([m(t) for t in i]) - data2 = websockets.Masker(b"abcd")(data) - assert data2 == b"".join(i) diff --git a/web/package.json b/web/package.json index fb2c8c30..59b031b9 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,7 @@ "start": "gulp" }, "jest": { - "testRegex": "__tests__/.*\\Spec.js$", + "testRegex": "__tests__/.*Spec.js$", "testPathDirs": [ "<rootDir>/src/js" ], @@ -19,16 +19,16 @@ "bootstrap": "^3.3.6", "classnames": "^2.2.5", "flux": "^2.1.1", + "history": "^3.0.0", "lodash": "^4.11.2", "react": "^15.1.0", + "react-codemirror": "^0.2.6", "react-dom": "^15.1.0", "react-redux": "^4.4.5", - "react-router": "^2.4.0", "redux": "^3.5.2", "redux-logger": "^2.6.1", "redux-thunk": "^2.1.0", - "shallowequal": "^0.2.2", - "react-codemirror": "^0.2.6" + "shallowequal": "^0.2.2" }, "devDependencies": { "babel-core": "^6.7.7", diff --git a/web/src/css/dropdown.less b/web/src/css/dropdown.less new file mode 100644 index 00000000..ba8442df --- /dev/null +++ b/web/src/css/dropdown.less @@ -0,0 +1,4 @@ +hr.divider { + margin-top: 5px; + margin-bottom: 5px; +} diff --git a/web/src/js/__tests__/ducks/ui/flowSpec.js b/web/src/js/__tests__/ducks/ui/flowSpec.js new file mode 100644 index 00000000..f838fbaa --- /dev/null +++ b/web/src/js/__tests__/ducks/ui/flowSpec.js @@ -0,0 +1,76 @@ +jest.unmock('../../../ducks/ui/flow') +jest.unmock('../../../ducks/flows') +jest.unmock('lodash') + +import _ from 'lodash' +import reducer, { + startEdit, + setContentViewDescription, + setShowFullContent, + setContent, + updateEdit + } from '../../../ducks/ui/flow' + +import { select, updateFlow } from '../../../ducks/flows' + +describe('flow reducer', () => { + it('should change to edit mode', () => { + let testFlow = {flow : 'foo'} + const newState = reducer(undefined, startEdit({ flow: 'foo' })) + expect(newState.contentView).toEqual('Edit') + expect(newState.modifiedFlow).toEqual(testFlow) + expect(newState.showFullContent).toEqual(true) + }) + it('should set the view description', () => { + expect(reducer(undefined, setContentViewDescription('description')).viewDescription) + .toEqual('description') + }) + + it('should set show full content', () => { + expect(reducer({showFullContent: false}, setShowFullContent()).showFullContent) + .toBeTruthy() + }) + + it('should set showFullContent to true', () => { + let maxLines = 10 + let content = _.range(maxLines) + const newState = reducer({maxContentLines: maxLines}, setContent(content) ) + expect(newState.showFullContent).toBeTruthy() + expect(newState.content).toEqual(content) + }) + + it('should set showFullContent to false', () => { + let maxLines = 5 + let content = _.range(maxLines+1); + const newState = reducer({maxContentLines: maxLines}, setContent(_.range(maxLines+1))) + expect(newState.showFullContent).toBeFalsy() + expect(newState.content).toEqual(content) + }) + + it('should not change the contentview mode', () => { + expect(reducer({contentView: 'foo'}, select(1)).contentView).toEqual('foo') + }) + + it('should change the contentview mode to auto after editing when a new flow will be selected', () => { + expect(reducer({contentView: 'foo', modifiedFlow : 'test_flow'}, select(1)).contentView).toEqual('Auto') + }) + + it('should set update and merge the modifiedflow with the update values', () => { + let modifiedFlow = {headers: []} + let updateValues = {content: 'bar'} + let result = {headers: [], content: 'bar'} + expect(reducer({modifiedFlow}, updateEdit(updateValues)).modifiedFlow).toEqual(result) + }) + + it('should not change the state when a flow is updated which is not selected', () => { + let modifiedFlow = {id: 1} + let updatedFlow = {id: 0} + expect(reducer({modifiedFlow}, updateFlow(updatedFlow)).modifiedFlow).toEqual(modifiedFlow) + }) + + it('should stop editing when the selected flow is updated', () => { + let modifiedFlow = {id: 1} + let updatedFlow = {id: 1} + expect(reducer({modifiedFlow}, updateFlow(updatedFlow)).modifiedFlow).toBeFalsy() + }) +}) diff --git a/web/src/js/__tests__/ducks/utils/listSpec.js b/web/src/js/__tests__/ducks/utils/listSpec.js index 72d162f2..0f5d0f34 100644 --- a/web/src/js/__tests__/ducks/utils/listSpec.js +++ b/web/src/js/__tests__/ducks/utils/listSpec.js @@ -2,6 +2,7 @@ jest.unmock('lodash') jest.unmock('../../../ducks/utils/list') import reduce, * as list from '../../../ducks/utils/list' +import _ from 'lodash' describe('list reduce', () => { diff --git a/web/src/js/app.jsx b/web/src/js/app.jsx index 726b2ae1..f04baea0 100644 --- a/web/src/js/app.jsx +++ b/web/src/js/app.jsx @@ -3,10 +3,8 @@ import { render } from 'react-dom' import { applyMiddleware, createStore } from 'redux' import { Provider } from 'react-redux' import thunk from 'redux-thunk' -import { Route, Router as ReactRouter, hashHistory, Redirect } from 'react-router' import ProxyApp from './components/ProxyApp' -import MainView from './components/MainView' import rootReducer from './ducks/index' import { add as addLog } from './ducks/eventLog' @@ -32,13 +30,7 @@ window.addEventListener('error', msg => { document.addEventListener('DOMContentLoaded', () => { render( <Provider store={store}> - <ReactRouter history={hashHistory}> - <Redirect from="/" to="/flows" /> - <Route path="/" component={ProxyApp}> - <Route path="flows" component={MainView}/> - <Route path="flows/:flowId/:detailTab" component={MainView}/> - </Route> - </ReactRouter> + <ProxyApp /> </Provider>, document.getElementById("mitmproxy") ) diff --git a/web/src/js/components/ContentView/CodeEditor.jsx b/web/src/js/components/ContentView/CodeEditor.jsx index d0430e6f..8afc128f 100644 --- a/web/src/js/components/ContentView/CodeEditor.jsx +++ b/web/src/js/components/ContentView/CodeEditor.jsx @@ -1,5 +1,4 @@ -import React, { Component, PropTypes } from 'react' -import { render } from 'react-dom'; +import React, {PropTypes} from 'react' import Codemirror from 'react-codemirror'; diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx index cd593023..32a07564 100644 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -30,6 +30,12 @@ function Edit({ content, onChange }) { Edit = ContentLoader(Edit) class ViewServer extends Component { + static propTypes = { + showFullContent: PropTypes.bool.isRequired, + maxLines: PropTypes.number.isRequired, + setContentViewDescription : PropTypes.func.isRequired, + setContent: PropTypes.func.isRequired + } componentWillMount(){ this.setContentView(this.props) @@ -40,6 +46,7 @@ class ViewServer extends Component { this.setContentView(nextProps) } } + setContentView(props){ try { this.data = JSON.parse(props.content) @@ -50,25 +57,31 @@ class ViewServer extends Component { props.setContentViewDescription(props.contentView != this.data.description ? this.data.description : '') props.setContent(this.data.lines) } + render() { const {content, contentView, message, maxLines} = this.props let lines = this.props.showFullContent ? this.data.lines : this.data.lines.slice(0, maxLines) - return <div> + return ( + <div> <pre> {lines.map((line, i) => <div key={`line${i}`}> - {line.map((tuple, j) => - <span key={`tuple${j}`} className={tuple[0]}> - {tuple[1]} - </span> - )} + {line.map((element, j) => { + let [style, text] = element + return ( + <span key={`tuple${j}`} className={style}> + {text} + </span> + ) + })} </div> )} </pre> - {ViewImage.matches(message) && - <ViewImage {...this.props} /> - } - </div> + {ViewImage.matches(message) && + <ViewImage {...this.props} /> + } + </div> + ) } } diff --git a/web/src/js/components/ContentView/ShowFullContentButton.jsx b/web/src/js/components/ContentView/ShowFullContentButton.jsx index 676068e9..cfd96dd8 100644 --- a/web/src/js/components/ContentView/ShowFullContentButton.jsx +++ b/web/src/js/components/ContentView/ShowFullContentButton.jsx @@ -16,7 +16,7 @@ function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLi return ( !showFullContent && <div> - <Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent(true)} text="Show full content"/> + <Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent()} text="Show full content"/> <span className="pull-right"> {visibleLines}/{contentLines} are visible </span> </div> ) diff --git a/web/src/js/components/ContentView/UploadContentButton.jsx b/web/src/js/components/ContentView/UploadContentButton.jsx index 0652b584..de349af4 100644 --- a/web/src/js/components/ContentView/UploadContentButton.jsx +++ b/web/src/js/components/ContentView/UploadContentButton.jsx @@ -1,28 +1,18 @@ import { PropTypes } from 'react' +import FileChooser from '../common/FileChooser' UploadContentButton.propTypes = { uploadContent: PropTypes.func.isRequired, } export default function UploadContentButton({ uploadContent }) { - - let fileInput; - + return ( - <a className="btn btn-default btn-xs" - onClick={() => fileInput.click()} - title="Upload a file to replace the content."> - <i className="fa fa-upload"/> - <input - ref={ref => fileInput = ref} - className="hidden" - type="file" - onChange={e => { - if (e.target.files.length > 0) uploadContent(e.target.files[0]) - }} - /> - </a> - + <FileChooser + icon="fa-upload" + title="Upload a file to replace the content." + onOpenFile={uploadContent} + className="btn btn-default btn-xs"/> ) } diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx index 59ec4276..ab433ea3 100644 --- a/web/src/js/components/ContentView/ViewSelector.jsx +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -1,72 +1,36 @@ import React, { PropTypes, Component } from 'react' -import classnames from 'classnames' import { connect } from 'react-redux' import * as ContentViews from './ContentViews' -import { setContentView } from "../../ducks/ui/flow"; - -function ViewItem({ name, setContentView, children }) { - return ( - <li> - <a href="#" onClick={() => setContentView(name)}> - {children} - </a> - </li> - ) -} +import { setContentView } from '../../ducks/ui/flow'; +import Dropdown from '../common/Dropdown' -/*ViewSelector.propTypes = { +ViewSelector.propTypes = { contentViews: PropTypes.array.isRequired, activeView: PropTypes.string.isRequired, isEdit: PropTypes.bool.isRequired, - isContentViewSelectorOpen: PropTypes.bool.isRequired, - setContentViewSelectorOpen: PropTypes.func.isRequired -}*/ - - -class ViewSelector extends Component { - constructor(props, context) { - super(props, context) - this.close = this.close.bind(this) - this.state = {open: false} - } - close() { - this.setState({open: false}) - document.removeEventListener('click', this.close) - } - - onDropdown(e){ - e.preventDefault() - this.setState({open: !this.state.open}) - document.addEventListener('click', this.close) - } + setContentView: PropTypes.func.isRequired +} - render() { - const {contentViews, activeView, isEdit, setContentView} = this.props - let edit = ContentViews.Edit.displayName +function ViewSelector ({contentViews, activeView, isEdit, setContentView}){ + let edit = ContentViews.Edit.displayName + let inner = <span> <b>View:</b> {activeView}<span className="caret"></span> </span> - return ( - <div className={classnames('dropup pull-left', { open: this.state.open })}> - <a className="btn btn-default btn-xs" - onClick={ e => this.onDropdown(e) } - href="#"> - <b>View:</b> {activeView}<span className="caret"></span> + return ( + <Dropdown dropup className="pull-left" btnClass="btn btn-default btn-xs" text={inner}> + {contentViews.map(name => + <a href="#" key={name} onClick={e => {e.preventDefault(); setContentView(name)}}> + {name.toLowerCase().replace('_', ' ')} </a> - <ul className="dropdown-menu" role="menu"> - {contentViews.map(name => - <ViewItem key={name} setContentView={setContentView} name={name}> - {name.toLowerCase().replace('_', ' ')} - </ViewItem> - )} - {isEdit && - <ViewItem key={edit} setContentView={setContentView} name={edit}> - {edit.toLowerCase()} - </ViewItem> - } - </ul> - </div> - ) - } + ) + } + {isEdit && + <a href="#" onClick={e => {e.preventDefault(); setContentView(edit)}}> + {edit.toLowerCase()} + </a> + } + </Dropdown> + ) } export default connect ( diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 2bda70e1..96e7b7db 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -7,40 +7,41 @@ Footer.propTypes = { } function Footer({ settings }) { + let {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings; return ( <footer> - {settings.mode && settings.mode != "regular" && ( - <span className="label label-success">{settings.mode} mode</span> + {mode && mode != "regular" && ( + <span className="label label-success">{mode} mode</span> )} - {settings.intercept && ( - <span className="label label-success">Intercept: {settings.intercept}</span> + {intercept && ( + <span className="label label-success">Intercept: {intercept}</span> )} - {settings.showhost && ( + {showhost && ( <span className="label label-success">showhost</span> )} - {settings.no_upstream_cert && ( + {no_upstream_cert && ( <span className="label label-success">no-upstream-cert</span> )} - {settings.rawtcp && ( + {rawtcp && ( <span className="label label-success">raw-tcp</span> )} - {!settings.http2 && ( + {!http2 && ( <span className="label label-success">no-http2</span> )} - {settings.anticache && ( + {anticache && ( <span className="label label-success">anticache</span> )} - {settings.anticomp && ( + {anticomp && ( <span className="label label-success">anticomp</span> )} - {settings.stickyauth && ( - <span className="label label-success">stickyauth: {settings.stickyauth}</span> + {stickyauth && ( + <span className="label label-success">stickyauth: {stickyauth}</span> )} - {settings.stickycookie && ( - <span className="label label-success">stickycookie: {settings.stickycookie}</span> + {stickycookie && ( + <span className="label label-success">stickycookie: {stickycookie}</span> )} - {settings.stream && ( - <span className="label label-success">stream: {formatSize(settings.stream)}</span> + {stream && ( + <span className="label label-success">stream: {formatSize(stream)}</span> )} </footer> ) diff --git a/web/src/js/components/Header/FileMenu.jsx b/web/src/js/components/Header/FileMenu.jsx index d3786475..53c63ea1 100644 --- a/web/src/js/components/Header/FileMenu.jsx +++ b/web/src/js/components/Header/FileMenu.jsx @@ -1,103 +1,46 @@ -import React, { Component } from 'react' +import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' -import classnames from 'classnames' +import FileChooser from '../common/FileChooser' +import Dropdown, {Divider} from '../common/Dropdown' import * as flowsActions from '../../ducks/flows' -class FileMenu extends Component { - - constructor(props, context) { - super(props, context) - this.state = { show: false } - - this.close = this.close.bind(this) - this.onFileClick = this.onFileClick.bind(this) - this.onNewClick = this.onNewClick.bind(this) - this.onOpenClick = this.onOpenClick.bind(this) - this.onOpenFile = this.onOpenFile.bind(this) - this.onSaveClick = this.onSaveClick.bind(this) - } - - close() { - this.setState({ show: false }) - document.removeEventListener('click', this.close) - } - - onFileClick(e) { - e.preventDefault() - - if (this.state.show) { - return - } - - document.addEventListener('click', this.close) - this.setState({ show: true }) - } - - onNewClick(e) { - e.preventDefault() - if (confirm('Delete all flows?')) { - this.props.clearFlows() - } - } - - onOpenClick(e) { - e.preventDefault() - this.fileInput.click() - } - - onOpenFile(e) { - e.preventDefault() - if (e.target.files.length > 0) { - this.props.loadFlows(e.target.files[0]) - this.fileInput.value = '' - } - } +FileMenu.propTypes = { + clearFlows: PropTypes.func.isRequired, + loadFlows: PropTypes.func.isRequired, + saveFlows: PropTypes.func.isRequired +} - onSaveClick(e) { - e.preventDefault() - this.props.saveFlows() - } +FileMenu.onNewClick = (e, clearFlows) => { + e.preventDefault(); + if (confirm('Delete all flows?')) + clearFlows() +} - render() { - return ( - <div className={classnames('dropdown pull-left', { open: this.state.show })}> - <a href="#" className="special" onClick={this.onFileClick}>mitmproxy</a> - <ul className="dropdown-menu" role="menu"> - <li> - <a href="#" onClick={this.onNewClick}> - <i className="fa fa-fw fa-file"></i> - New - </a> - </li> - <li> - <a href="#" onClick={this.onOpenClick}> - <i className="fa fa-fw fa-folder-open"></i> - Open... - </a> - <input - ref={ref => this.fileInput = ref} - className="hidden" - type="file" - onChange={this.onOpenFile} - /> - </li> - <li> - <a href="#" onClick={this.onSaveClick}> - <i className="fa fa-fw fa-floppy-o"></i> - Save... - </a> - </li> - <li role="presentation" className="divider"></li> - <li> - <a href="http://mitm.it/" target="_blank"> - <i className="fa fa-fw fa-external-link"></i> - Install Certificates... - </a> - </li> - </ul> - </div> - ) - } +function FileMenu ({clearFlows, loadFlows, saveFlows}) { + return ( + <Dropdown className="pull-left" btnClass="special" text="mitmproxy"> + <a href="#" onClick={e => FileMenu.onNewClick(e, clearFlows)}> + <i className="fa fa-fw fa-file"></i> + New + </a> + <FileChooser + icon="fa-folder-open" + text="Open..." + onOpenFile={file => loadFlows(file)} + /> + <a href="#" onClick={e =>{ e.preventDefault(); saveFlows();}}> + <i className="fa fa-fw fa-floppy-o"></i> + Save... + </a> + + <Divider/> + + <a href="http://mitm.it/" target="_blank"> + <i className="fa fa-fw fa-external-link"></i> + Install Certificates... + </a> + </Dropdown> + ) } export default connect( diff --git a/web/src/js/components/Header/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index bdd30d5e..e78a49aa 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -8,10 +8,14 @@ FlowMenu.title = 'Flow' FlowMenu.propTypes = { flow: PropTypes.object.isRequired, + acceptFlow: PropTypes.func.isRequired, + replayFlow: PropTypes.func.isRequired, + duplicateFlow: PropTypes.func.isRequired, + removeFlow: PropTypes.func.isRequired, + revertFlow: PropTypes.func.isRequired } function FlowMenu({ flow, acceptFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) { - return ( <div> <div className="menu-row"> diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index a338fed0..a11062f2 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -41,17 +41,17 @@ function OptionMenu({ settings, updateSettings }) { /> <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" checked={!!settings.stickyauth} - txt={settings.stickyauth || ''} + txt={settings.stickyauth} onToggleChanged={txt => updateSettings({ stickyauth: !settings.stickyauth ? txt : null })} /> <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter" checked={!!settings.stickycookie} - txt={settings.stickycookie || ''} + txt={settings.stickycookie} onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })} /> <ToggleInputButton name="stream" placeholder="stream..." checked={!!settings.stream} - txt={settings.stream || ''} + txt={settings.stream} inputType="number" onToggleChanged={txt => updateSettings({ stream: !settings.stream ? txt : null })} /> diff --git a/web/src/js/components/MainView.jsx b/web/src/js/components/MainView.jsx index f45f9eef..8be6f21c 100644 --- a/web/src/js/components/MainView.jsx +++ b/web/src/js/components/MainView.jsx @@ -29,8 +29,7 @@ class MainView extends Component { <FlowView key="flowDetails" ref="flowDetails" - tab={this.props.routeParams.detailTab} - query={this.props.query} + tab={this.props.tab} updateFlow={data => this.props.updateFlow(selectedFlow, data)} flow={selectedFlow} /> @@ -45,7 +44,8 @@ export default connect( flows: state.flowView.data, filter: state.flowView.filter, highlight: state.flowView.highlight, - selectedFlow: state.flows.byId[state.flows.selected[0]] + selectedFlow: state.flows.byId[state.flows.selected[0]], + tab: state.ui.flow.tab, }), { selectFlow: flowsActions.select, diff --git a/web/src/js/components/ProxyApp.jsx b/web/src/js/components/ProxyApp.jsx index f8a6e262..d76816e5 100644 --- a/web/src/js/components/ProxyApp.jsx +++ b/web/src/js/components/ProxyApp.jsx @@ -1,58 +1,77 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' +import { createHashHistory, useQueries } from 'history' import { init as appInit, destruct as appDestruct } from '../ducks/app' import { onKeyDown } from '../ducks/ui/keyboard' +import { updateFilter, updateHighlight } from '../ducks/flowView' +import { selectTab } from '../ducks/ui/flow' +import { select as selectFlow } from '../ducks/flows' +import { Query } from '../actions' +import MainView from './MainView' import Header from './Header' import EventLog from './EventLog' import Footer from './Footer' class ProxyAppMain extends Component { - static contextTypes = { - router: PropTypes.object.isRequired, + flushToStore(location) { + const components = location.pathname.split('/').filter(v => v) + const query = location.query || {} + + if (components.length > 2) { + this.props.selectFlow(components[1]) + this.props.selectTab(components[2]) + } else { + this.props.selectFlow(null) + this.props.selectTab(null) + } + + this.props.updateFilter(query[Query.SEARCH]) + this.props.updateHighlight(query[Query.HIGHLIGHT]) + } + + flushToHistory(props) { + const query = { ...query } + + if (props.filter) { + query[Query.SEARCH] = props.filter + } + + if (props.highlight) { + query[Query.HIGHLIGHT] = props.highlight + } + + if (props.selectedFlowId) { + this.history.push({ pathname: `/flows/${props.selectedFlowId}/${props.tab}`, query }) + } else { + this.history.push({ pathname: '/flows', query }) + } } componentWillMount() { - this.props.appInit(this.context.router) + this.props.appInit() + this.history = useQueries(createHashHistory)() + this.unlisten = this.history.listen(location => this.flushToStore(location)) window.addEventListener('keydown', this.props.onKeyDown); } componentWillUnmount() { - this.props.appDestruct(this.context.router) + this.props.appDestruct() + this.unlisten() window.removeEventListener('keydown', this.props.onKeyDown); } componentWillReceiveProps(nextProps) { - /* - FIXME: improve react-router -> redux integration. - if (nextProps.location.query[Query.SEARCH] !== nextProps.filter) { - this.props.updateFilter(nextProps.location.query[Query.SEARCH], false) - } - if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { - this.props.updateHighlight(nextProps.location.query[Query.HIGHLIGHT], false) - } - */ - if (nextProps.query === this.props.query && nextProps.selectedFlowId === this.props.selectedFlowId && nextProps.panel === this.props.panel) { - return - } - if (nextProps.selectedFlowId) { - this.context.router.replace({ pathname: `/flows/${nextProps.selectedFlowId}/${nextProps.panel}`, query: nextProps.query }) - } else { - this.context.router.replace({ pathname: '/flows', query: nextProps.query }) - } - + this.flushToHistory(nextProps) } render() { - const { showEventLog, location, children, query } = this.props + const { showEventLog, location, filter, highlight } = this.props return ( <div id="container" tabIndex="0"> <Header/> - {React.cloneElement( - children, - { ref: 'view', location, query } - )} + <MainView /> {showEventLog && ( <EventLog key="eventlog"/> )} @@ -65,13 +84,18 @@ class ProxyAppMain extends Component { export default connect( state => ({ showEventLog: state.eventLog.visible, - query: state.flowView.filter, - panel: state.ui.flow.tab, + filter: state.flowView.filter, + highlight: state.flowView.highlight, + tab: state.ui.flow.tab, selectedFlowId: state.flows.selected[0] }), { appInit, appDestruct, - onKeyDown + onKeyDown, + updateFilter, + updateHighlight, + selectTab, + selectFlow } )(ProxyAppMain) diff --git a/web/src/js/components/common/Dropdown.jsx b/web/src/js/components/common/Dropdown.jsx new file mode 100644 index 00000000..cc95a6dc --- /dev/null +++ b/web/src/js/components/common/Dropdown.jsx @@ -0,0 +1,53 @@ +import React, { Component, PropTypes } from 'react' +import classnames from 'classnames' + +export const Divider = () => <hr className="divider"/> + +export default class Dropdown extends Component { + + static propTypes = { + dropup: PropTypes.bool, + className: PropTypes.string, + btnClass: PropTypes.string.isRequired + } + + static defaultProps = { + dropup: false + } + + constructor(props, context) { + super(props, context) + this.state = { open: false } + this.close = this.close.bind(this) + this.open = this.open.bind(this) + } + + close() { + this.setState({ open: false }) + document.removeEventListener('click', this.close) + } + + open(e){ + e.preventDefault() + if (this.state.open) { + return + } + this.setState({open: !this.state.open}) + document.addEventListener('click', this.close) + } + + render() { + const {dropup, className, btnClass, text, children} = this.props + return ( + <div className={classnames( (dropup ? 'dropup' : 'dropdown'), className, { open: this.state.open })}> + <a href='#' className={btnClass} + onClick={this.open}> + {text} + </a> + <ul className="dropdown-menu" role="menu"> + {children.map ( (item, i) => <li key={i}> {item} </li> )} + </ul> + </div> + ) + } +} diff --git a/web/src/js/components/common/FileChooser.jsx b/web/src/js/components/common/FileChooser.jsx new file mode 100644 index 00000000..d59d2d6d --- /dev/null +++ b/web/src/js/components/common/FileChooser.jsx @@ -0,0 +1,27 @@ +import React, { PropTypes } from 'react' + +FileChooser.propTypes = { + icon: PropTypes.string, + text: PropTypes.string, + className: PropTypes.string, + title: PropTypes.string, + onOpenFile: PropTypes.func.isRequired +} + +export default function FileChooser({ icon, text, className, title, onOpenFile }) { + let fileInput; + return ( + <a href='#' onClick={() => fileInput.click()} + className={className} + title={title}> + <i className={'fa fa-fw ' + icon}></i> + {text} + <input + ref={ref => fileInput = ref} + className="hidden" + type="file" + onChange={e => { e.preventDefault(); if(e.target.files.length > 0) onOpenFile(e.target.files[0]); fileInput = "";}} + /> + </a> + ) +} diff --git a/web/src/js/components/common/ToggleInputButton.jsx b/web/src/js/components/common/ToggleInputButton.jsx index 25d620ae..5fa24c10 100644 --- a/web/src/js/components/common/ToggleInputButton.jsx +++ b/web/src/js/components/common/ToggleInputButton.jsx @@ -6,17 +6,16 @@ export default class ToggleInputButton extends Component { static propTypes = { name: PropTypes.string.isRequired, - txt: PropTypes.string.isRequired, - onToggleChanged: PropTypes.func.isRequired + txt: PropTypes.string, + onToggleChanged: PropTypes.func.isRequired, + checked: PropTypes.bool.isRequired, + placeholder: PropTypes.string.isRequired, + inputType: PropTypes.string } constructor(props) { super(props) - this.state = { txt: props.txt } - } - - onChange(e) { - this.setState({ txt: e.target.value }) + this.state = { txt: props.txt || '' } } onKeyDown(e) { @@ -27,23 +26,24 @@ export default class ToggleInputButton extends Component { } render() { + const {checked, onToggleChanged, name, inputType, placeholder} = this.props return ( <div className="input-group toggle-input-btn"> <span className="input-group-btn" - onClick={() => this.props.onToggleChanged(this.state.txt)}> - <div className={classnames('btn', this.props.checked ? 'btn-primary' : 'btn-default')}> - <span className={classnames('fa', this.props.checked ? 'fa-check-square-o' : 'fa-square-o')}/> + onClick={() => onToggleChanged(this.state.txt)}> + <div className={classnames('btn', checked ? 'btn-primary' : 'btn-default')}> + <span className={classnames('fa', checked ? 'fa-check-square-o' : 'fa-square-o')}/> - {this.props.name} + {name} </div> </span> <input className="form-control" - placeholder={this.props.placeholder} - disabled={this.props.checked} + placeholder={placeholder} + disabled={checked} value={this.state.txt} - type={this.props.inputType} - onChange={e => this.onChange(e)} + type={inputType || 'text'} + onChange={e => this.setState({ txt: e.target.value })} onKeyDown={e => this.onKeyDown(e)} /> </div> diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index f96653a9..404db0d1 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -1,5 +1,6 @@ import { fetchApi } from '../utils' import reduceList, * as listActions from './utils/list' +import { selectRelative } from './flowView' import * as msgQueueActions from './msgQueue' import * as websocketActions from './websocket' @@ -210,5 +211,14 @@ export function updateFlow(item) { * @private */ export function removeFlow(id) { - return { type: REMOVE, id } + return (dispatch, getState) => { + let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]] + let maxIndex = getState().flowView.data.length - 1 + let deleteLastEntry = maxIndex == 0 + if (deleteLastEntry) + dispatch(select()) + else + dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) ) + dispatch({ type: REMOVE, id }) + } } diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js index fb2a846d..4a6d64cd 100644 --- a/web/src/js/ducks/ui/flow.js +++ b/web/src/js/ducks/ui/flow.js @@ -16,7 +16,7 @@ export const SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW', const defaultState = { displayLarge: false, - contentViewDescription: '', + viewDescription: '', showFullContent: false, modifiedFlow: false, contentView: 'Auto', @@ -27,6 +27,10 @@ const defaultState = { export default function reducer(state = defaultState, action) { let wasInEditMode = !!(state.modifiedFlow) + + let content = action.content || state.content + let isFullContentShown = content && content.length <= state.maxContentLines + switch (action.type) { case START_EDIT: @@ -49,8 +53,7 @@ export default function reducer(state = defaultState, action) { modifiedFlow: false, displayLarge: false, contentView: (wasInEditMode ? 'Auto' : state.contentView), - viewDescription: '', - showFullContent: false, + showFullContent: isFullContentShown, } case flowsActions.UPDATE: @@ -63,7 +66,6 @@ export default function reducer(state = defaultState, action) { modifiedFlow: false, displayLarge: false, contentView: (wasInEditMode ? 'Auto' : state.contentView), - viewDescription: '', showFullContent: false } } else { @@ -79,13 +81,13 @@ export default function reducer(state = defaultState, action) { case SET_SHOW_FULL_CONTENT: return { ...state, - showFullContent: action.show + showFullContent: true } case SET_TAB: return { ...state, - tab: action.tab, + tab: action.tab ? action.tab : 'request', displayLarge: false, showFullContent: false } @@ -98,7 +100,6 @@ export default function reducer(state = defaultState, action) { } case SET_CONTENT: - let isFullContentShown = action.content.length < state.maxContentLines return { ...state, content: action.content, @@ -139,12 +140,8 @@ export function setContentViewDescription(description) { return { type: SET_CONTENT_VIEW_DESCRIPTION, description } } -export function setShowFullContent(show) { - return { type: SET_SHOW_FULL_CONTENT, show } -} - -export function updateEdit(update) { - return { type: UPDATE_EDIT, update } +export function setShowFullContent() { + return { type: SET_SHOW_FULL_CONTENT } } export function setContent(content){ @@ -152,6 +149,5 @@ export function setContent(content){ } export function stopEdit(flow, modifiedFlow) { - let diff = getDiff(flow, modifiedFlow) - return flowsActions.update(flow, diff) + return flowsActions.update(flow, getDiff(flow, modifiedFlow)) } diff --git a/web/src/js/utils.js b/web/src/js/utils.js index e44182d0..e8470cec 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -107,14 +107,15 @@ fetchApi.put = (url, json, options) => fetchApi( ...options } ) - +// deep comparison of two json objects (dicts). arrays are handeled as a single value. +// return: json object including only the changed keys value pairs. export function getDiff(obj1, obj2) { let result = {...obj2}; for(let key in obj1) { if(_.isEqual(obj2[key], obj1[key])) result[key] = undefined - else if(!(Array.isArray(obj2[key]) && Array.isArray(obj1[key])) && - typeof obj2[key] == 'object' && typeof obj1[key] == 'object') + else if(Object.prototype.toString.call(obj2[key]) === '[object Object]' && + Object.prototype.toString.call(obj1[key]) === '[object Object]' ) result[key] = getDiff(obj1[key], obj2[key]) } return result |