diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a69a0a8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,391 @@ +root = true + +[*] +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = false +max_line_length = 120 +charset = utf-8 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_inside_block_braces_when_body_is_present = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.json,*.jsonc}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitignore b/.gitignore index 43936ae..810bf15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -.gradle .idea +target out build __pycache__ +/.mvn/wrapper/maven-wrapper.jar diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 0000000..6686a64 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6b779fb..0000000 --- a/build.gradle +++ /dev/null @@ -1,104 +0,0 @@ -plugins { - id 'java-library' - id 'signing' - id 'maven' -} - -group = 'io.appium' -version = '2.0.2' -archivesBaseName = 'mitmproxy-java' - -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.java-websocket:Java-WebSocket:1.4.0' - implementation group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0-m02' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.8' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - implementation group: 'org.zeroturnaround', name: 'zt-exec', version: '1.10' - implementation group: 'org.zeroturnaround', name: 'zt-process-killer', version: '1.9' - implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' - testCompile group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9' - testCompile group: 'org.assertj', name: 'assertj-core', version: '3.12.2' - - compileOnly 'org.projectlombok:lombok:1.18.8' - annotationProcessor 'org.projectlombok:lombok:1.18.8' - - compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.0' -} - -task javadocJar(type: Jar) { - classifier = 'javadoc' - from javadoc -} -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource -} -artifacts { - archives javadocJar, sourcesJar -} -signing { - setRequired { - // condition for when signing is required - gradle.taskGraph.hasTask("uploadArchives") - } - sign configurations.archives -} - - -// Build, sign, and upload -uploadArchives { - repositories { - mavenDeployer { - - // Sign POM - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - // Destination - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: 'username', password: 'password') - } - snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: 'username', password: 'password') - } - - // Add required metadata to POM - pom.project { - name 'mitmproxy-java' - packaging 'jar' - description 'A bridge between Python\'s mitmproxy and Java programs. Built on top of mitmproxy-node' - url 'https://github.com/appium/mitmproxy-java' - - scm { - connection 'scm:git:git://github.com/appium/mitmproxy-java.git' - developerConnection 'scm:git:ssh://github.com/appium/mitmproxy-java.git' - url 'http://github.com/appium/mitmproxy-java/tree/master' - } - - licenses { - license { - name 'The Apache License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - developers { - developer { - id 'jonahss' - name 'Jonah Stiennon' - email 'jonah@cloudgrey.io' - organization 'Cloud Grey' - organizationUrl 'https://cloudgrey.io' - } - } - } - } - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 28861d2..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d9cef9d..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Apr 11 14:32:28 PDT 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip diff --git a/gradlew b/gradlew deleted file mode 100755 index cccdd3d..0000000 --- a/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index e95643d..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..b7f0646 --- /dev/null +++ b/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100755 index 0000000..474c9d6 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4479bb8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,253 @@ + + + 4.0.0 + + io.appium + mitmproxy-java + 3.0.2 + jar + + mitmproxy-java + A bridge between Python's mitmproxy and Java programs. Built on top of mitmproxy-node + https://github.com/appium/mitmproxy-java + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + jonahss + Jonah Stiennon + jonah@cloudgrey.io + Cloud Grey + https://cloudgrey.io + + + + + scm:git:git://github.com/appium/mitmproxy-java.git + scm:git:ssh://github.com/appium/mitmproxy-java.git + http://github.com/appium/mitmproxy-java/tree/master + + + + 21 + 21 + UTF-8 + 1.18.30 + + + + + org.java-websocket + Java-WebSocket + 1.5.7 + + + + javax.ws.rs + javax.ws.rs-api + 2.1.1 + + + + com.fasterxml.jackson.core + jackson-core + 2.21.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.21.1 + + + + org.zeroturnaround + zt-exec + 1.12 + + + + org.zeroturnaround + zt-process-killer + 1.11 + + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + + + + + + org.apache.commons + commons-lang3 + 3.20.0 + + + + commons-io + commons-io + 2.22.0 + + + + org.slf4j + slf4j-simple + 2.0.18 + + + + org.apache.commons + commons-collections4 + 4.5.0 + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 5.14.2 + test + + + + com.konghq + unirest-java-core + 4.9.0 + test + + + + org.assertj + assertj-core + 3.27.7 + test + + + + com.google.guava + guava + 33.0.0-jre + + + + + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + mitmproxy-java + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + jar-no-fork + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + attach-javadocs + + jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 3222266..0000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'mitmproxy-java' - diff --git a/settings.xml b/settings.xml new file mode 100755 index 0000000..832e6a2 --- /dev/null +++ b/settings.xml @@ -0,0 +1,56 @@ + + + + + ${env.TII_JFROG_USERNAME} + ${env.TII_JFROG_PASSWORD} + central + + + ${env.TII_JFROG_USERNAME} + ${env.TII_JFROG_PASSWORD} + snapshots + + + + + + + + false + + central + libs-release + https://turnitin.jfrog.io:443/artifactory/libs-release + + + + snapshots + libs-snapshot + https://turnitin.jfrog.io:443/artifactory/libs-snapshot + + + + + + false + + central + libs-plugin-release + https://turnitin.jfrog.io:443/artifactory/libs-release + + + + snapshots + libs-plugin-snapshots + https://turnitin.jfrog.io:443/artifactory/libs-snapshots + + + artifactory + + + + artifactory + + diff --git a/src/main/java/io/appium/mitmproxy/InterceptedMessage.java b/src/main/java/io/appium/mitmproxy/InterceptedMessage.java index 18f347f..d066a5b 100644 --- a/src/main/java/io/appium/mitmproxy/InterceptedMessage.java +++ b/src/main/java/io/appium/mitmproxy/InterceptedMessage.java @@ -9,34 +9,34 @@ @Data public class InterceptedMessage { - private final static ObjectMapper objectMapper = new ObjectMapper(); + private final static ObjectMapper objectMapper = new ObjectMapper(); - private Request request; + private Request request; - private Response response; + private Response response; - @Data - public static class Request { + @Data + public static class Request { - private String method; + private String method; - private String url; + private String url; - private List headers; + private List headers; - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private byte[] body; - } + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private byte[] body; + } - @Data - public static class Response { + @Data + public static class Response { - @JsonProperty("status_code") - private int statusCode; + @JsonProperty("status_code") + private int statusCode; - private List headers; + private List headers; - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private byte[] body; - } + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private byte[] body; + } } \ No newline at end of file diff --git a/src/main/java/io/appium/mitmproxy/MessageCollector.java b/src/main/java/io/appium/mitmproxy/MessageCollector.java new file mode 100644 index 0000000..ed1dc12 --- /dev/null +++ b/src/main/java/io/appium/mitmproxy/MessageCollector.java @@ -0,0 +1,64 @@ +package io.appium.mitmproxy; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; + +/** + * Drop-in replacement for the pattern shown in the project README: + * + *
{@code
+ * List messages = new ArrayList<>();
+ * proxy = new MitmproxyJava(path, m -> { messages.add(m); return m; }, port, params);
+ * }
+ *

+ * That pattern is not thread-safe: {@link MitmproxyServer} can invoke the + * interceptor concurrently from multiple connection threads whenever more + * than one HTTP transaction is in flight at once, and a plain {@code + * ArrayList} does not support concurrent modification safely (you'll see + * {@code ConcurrentModificationException}, lost entries, or worse). + *

+ * This class wraps a {@link CopyOnWriteArrayList}, which is safe for exactly + * this "many concurrent writers, occasional reader" access pattern (frequent + * small appends, infrequent full reads of everything collected so far). + *

+ * Usage: + *

{@code
+ * ThreadSafeMessageCollector collector = new ThreadSafeMessageCollector();
+ * proxy = new MitmproxyJava(path, collector, port, params);
+ * proxy.start();
+ * // ... exercise the app under test ...
+ * proxy.stop();
+ * List messages = collector.getMessages(); // safe snapshot
+ * }
+ */ +public class MessageCollector implements Function { + + private final List messages = new CopyOnWriteArrayList<>(); + + @Override + public InterceptedMessage apply(InterceptedMessage interceptedMessage) { + messages.add(interceptedMessage); + // Returning the message unmodified passes the request straight through. + // If you need to rewrite requests/responses, do it here and return the + // modified message instead — just make sure any additional state you + // touch is also thread-safe. + return interceptedMessage; + } + + /** + * @return a live, thread-safe view of everything collected so far. Safe to + * iterate even while the proxy is still running and appending to it + * concurrently (iteration snapshots the list at the time it starts). + */ + public List getMessages() { + return messages; + } + + /** + * Reset for reuse — call after harvesting the messages for one test, before the next test reuses this collector. + */ + public void clear() { + messages.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/io/appium/mitmproxy/MessageSerializer.java b/src/main/java/io/appium/mitmproxy/MessageSerializer.java index 2429c65..d99cc9e 100644 --- a/src/main/java/io/appium/mitmproxy/MessageSerializer.java +++ b/src/main/java/io/appium/mitmproxy/MessageSerializer.java @@ -5,54 +5,53 @@ import lombok.SneakyThrows; import java.io.IOException; -import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class MessageSerializer { - private final static ObjectMapper objectMapper = new ObjectMapper(); - private static final int START_BYTES = 8; + private final static ObjectMapper objectMapper = new ObjectMapper(); + private static final int START_BYTES = 8; - @SneakyThrows(IOException.class) - public InterceptedMessage deserializeMessage(ByteBuffer buffer) { - buffer.order(ByteOrder.LITTLE_ENDIAN); + @SneakyThrows(IOException.class) + public InterceptedMessage deserializeMessage(ByteBuffer buffer) { + buffer.order(ByteOrder.LITTLE_ENDIAN); - int metadataSize = buffer.getInt(); - int request_content_size = buffer.getInt(); - int response_content_size = buffer.getInt(); + int metadataSize = buffer.getInt(); + int request_content_size = buffer.getInt(); + int response_content_size = buffer.getInt(); - byte[] metadataBytes = new byte[metadataSize]; - buffer.get(metadataBytes); + byte[] metadataBytes = new byte[metadataSize]; + buffer.get(metadataBytes); - byte[] requestBody = new byte[request_content_size]; - buffer.get(requestBody); + byte[] requestBody = new byte[request_content_size]; + buffer.get(requestBody); - byte[] responseBody = new byte[response_content_size]; - buffer.get(responseBody); + byte[] responseBody = new byte[response_content_size]; + buffer.get(responseBody); - InterceptedMessage interceptedMessage = objectMapper.readValue(metadataBytes, InterceptedMessage.class); - interceptedMessage.getRequest().setBody(requestBody); - interceptedMessage.getResponse().setBody(responseBody); + InterceptedMessage interceptedMessage = objectMapper.readValue(metadataBytes, InterceptedMessage.class); + interceptedMessage.getRequest().setBody(requestBody); + interceptedMessage.getResponse().setBody(responseBody); - return interceptedMessage; - } + return interceptedMessage; + } - public ByteBuffer serializeMessage(InterceptedMessage message) throws JsonProcessingException { - byte[] responseBody = message.getResponse().getBody(); + public ByteBuffer serializeMessage(InterceptedMessage message) throws JsonProcessingException { + byte[] responseBody = message.getResponse().getBody(); - int contentLength = responseBody.length; + int contentLength = responseBody.length; - byte[] metadata = objectMapper.writeValueAsBytes(message.getResponse()); - int metadataLength = metadata.length; + byte[] metadata = objectMapper.writeValueAsBytes(message.getResponse()); + int metadataLength = metadata.length; - ByteBuffer buffer = ByteBuffer.allocate(START_BYTES + metadataLength + contentLength); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(metadataLength); - buffer.putInt(contentLength); - buffer.put(metadata); - buffer.put(message.getResponse().getBody()); + ByteBuffer buffer = ByteBuffer.allocate(START_BYTES + metadataLength + contentLength); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(metadataLength); + buffer.putInt(contentLength); + buffer.put(metadata); + buffer.put(message.getResponse().getBody()); - return (ByteBuffer) buffer.rewind(); - } + return buffer.rewind(); + } } diff --git a/src/main/java/io/appium/mitmproxy/MitmproxyJava.java b/src/main/java/io/appium/mitmproxy/MitmproxyJava.java index 2da256a..345add7 100644 --- a/src/main/java/io/appium/mitmproxy/MitmproxyJava.java +++ b/src/main/java/io/appium/mitmproxy/MitmproxyJava.java @@ -1,5 +1,6 @@ package io.appium.mitmproxy; +import com.google.common.util.concurrent.Uninterruptibles; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; @@ -12,12 +13,15 @@ import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.Socket; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; @@ -25,128 +29,223 @@ @Slf4j public class MitmproxyJava { - private static final String LOCALHOST_IP = "127.0.0.1"; - private static final int WEBSOCKET_PORT = 8765; - private static final int TIMEOUT_FOR_SOCKET_CHECKING_MINS = 5; + private static final String LOCALHOST_IP = "127.0.0.1"; + private static final int TIMEOUT_FOR_SOCKET_CHECKING_MINS = 5; - private final String mitmproxyPath; + private final String mitmproxyPath; - private final Function messageInterceptor; + private final Function messageInterceptor; - private final int proxyPort; + // The port requested at construction time. 0 means "pick any free port" — + // resolved into actualProxyPort below, once start() actually binds it. + private final int proxyPort; - private MitmproxyServer server; + // The port actually used once started — same as proxyPort if an explicit + // port was requested, or the OS-assigned free port if proxyPort was 0. + // Exposed via getProxyPort() so callers (e.g. wiring up a browser's proxy + // capability) can find out which port to point at, especially important + // when running multiple instances in parallel (e.g. parallel test threads) + // where each one needs its own port rather than colliding on a shared one. + private volatile int actualProxyPort = -1; - private final List extraMitmdumpParams; + // volatile: start() and stop() are frequently called from different threads + // (e.g. a test's @Before/@After running on the test runner thread vs. a + // teardown/shutdown-hook thread). Without volatile, one thread may never + // observe the write the other thread made to these fields. + private volatile MitmproxyServer server; - private Future mitmproxyProcess; + private final List extraMitmdumpParams; - public MitmproxyJava(String mitmproxyPath, Function messageInterceptor, int proxyPort, List extraMitmdumpParams) { - this.mitmproxyPath = mitmproxyPath; - this.messageInterceptor = messageInterceptor; - this.proxyPort = proxyPort; - this.extraMitmdumpParams = extraMitmdumpParams; - } + private volatile Future mitmproxyProcess; + + // Guards against concurrent/duplicate start() or stop() calls, and against + // stop() being called before start() (or twice in a row) corrupting state. + private final AtomicBoolean isRunning = new AtomicBoolean(false); + + public MitmproxyJava(String mitmproxyPath, Function messageInterceptor, int proxyPort, List extraMitmdumpParams) { + this.mitmproxyPath = mitmproxyPath; + this.messageInterceptor = messageInterceptor; + this.proxyPort = proxyPort; + this.extraMitmdumpParams = extraMitmdumpParams; + } + + public MitmproxyJava(String mitmproxyPath, Function messageInterceptor) { + this(mitmproxyPath, messageInterceptor, 8080, null); + } - public MitmproxyJava(String mitmproxyPath, Function messageInterceptor) { - this(mitmproxyPath, messageInterceptor, 8080, null); + /** + * @return the port mitmdump is actually listening on for HTTP(S) traffic, + * once start() has completed. -1 if start() hasn't been called yet. + */ + public int getProxyPort() { + return actualProxyPort; + } + + + public synchronized MitmproxyJava start() throws IOException, TimeoutException { + if (!isRunning.compareAndSet(false, true)) { + throw new IllegalStateException("MitmproxyJava is already running; call stop() first"); } - public MitmproxyJava start() throws IOException, TimeoutException { - log.info("Starting mitmproxy on port {}", proxyPort); - - server = new MitmproxyServer(new InetSocketAddress(LOCALHOST_IP, WEBSOCKET_PORT), messageInterceptor); - server.start(); - - // python script file is zipped inside our jar. extract it into a temporary file. - String pythonScriptPath = extractPythonScriptToFile(); - - final List mitmproxyStartParams = new ArrayList<>(); - mitmproxyStartParams.add(mitmproxyPath); - mitmproxyStartParams.add("--anticache"); - mitmproxyStartParams.add("-p"); - mitmproxyStartParams.add(String.valueOf(proxyPort)); - mitmproxyStartParams.add("-s"); - mitmproxyStartParams.add(pythonScriptPath); - - // adding params if needed for mitmproxy - if (isNotEmpty(this.extraMitmdumpParams)) { - mitmproxyStartParams.addAll(this.extraMitmdumpParams); - } - - mitmproxyProcess = new ProcessExecutor() - .command(mitmproxyStartParams) - .redirectOutput(Slf4jStream.ofCaller().asInfo()) - .destroyOnExit() - .start() - .getFuture(); - - waitForPortToBeInUse(proxyPort); - log.info("Mitmproxy started on port {}", proxyPort); - return this; + log.info("Starting mitmproxy on port {}", proxyPort); + + try { + // Resolve the port to actually use: an explicit port is used as-is + // (unchanged behavior for existing callers); 0 means "pick a free + // one". This MUST happen before anything below uses a port number — + // findFreePort() returns a real, currently-unused port, whereas the + // raw value 0 can only be used for binding, not for the + // waitForPortToBeInUse/waitForPortToBeFree polling checks below, + // which need an actual port number to connect to. + actualProxyPort = (proxyPort == 0) ? findFreePort() : proxyPort; + + log.info("Starting mitmproxy on port {}", actualProxyPort); + + // Each MitmproxyJava instance gets its OWN free port for the Java<->Python + // control channel, instead of the old hardcoded 8765. Without this, two + // instances running at the same time (e.g. parallel test execution) would + // both try to bind the same port and the second one would fail with + // "Address already in use". + int websocketPort = findFreePort(); + + server = new MitmproxyServer(new InetSocketAddress(LOCALHOST_IP, websocketPort), messageInterceptor); + server.start(); + + // python script file is zipped inside our jar. extract it into a temporary file. + String pythonScriptPath = extractPythonScriptToFile(); + + final List mitmproxyStartParams = new ArrayList<>(); + mitmproxyStartParams.add(mitmproxyPath); + mitmproxyStartParams.add("--anticache"); + mitmproxyStartParams.add("-p"); + mitmproxyStartParams.add(String.valueOf(proxyPort)); + mitmproxyStartParams.add("-s"); + mitmproxyStartParams.add(pythonScriptPath); + // Tell proxy.py which port to connect back to for the control channel — + // must match the port MitmproxyServer just bound to above. + mitmproxyStartParams.add("--set"); + mitmproxyStartParams.add("wsPort=" + websocketPort); + + // adding params if needed for mitmproxy + if (isNotEmpty(this.extraMitmdumpParams)) { + mitmproxyStartParams.addAll(this.extraMitmdumpParams); + } + + mitmproxyProcess = new ProcessExecutor() + .command(mitmproxyStartParams) + .redirectOutput(Slf4jStream.ofCaller().asInfo()) + .destroyOnExit() + .start() + .getFuture(); + + waitForPortToBeInUse(actualProxyPort); + log.info("Mitmproxy started on port {}", actualProxyPort); + return this; + } catch (IOException | TimeoutException | RuntimeException e) { + // Roll back the "running" flag so a failed start() can be retried + // instead of permanently wedging this instance in a started state. + isRunning.set(false); + actualProxyPort = -1; + throw e; } + } + + /** + * Asks the OS for a free ephemeral port by briefly binding a ServerSocket + * to port 0, reading back the port it was assigned, then releasing it. + * There's a small unavoidable window between releasing this socket and + * MitmproxyServer binding to the same port where another process could + * theoretically grab it first — but this is the standard, practical way + * to get a free port in Java, and is a large improvement over the + * previous hardcoded-for-the-whole-JVM port. + */ + private static int findFreePort() throws IOException { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } + } - private String extractPythonScriptToFile() throws IOException { - File outfile = File.createTempFile("mitmproxy-python-plugin", ".py"); + private String extractPythonScriptToFile() throws IOException { + File outfile = File.createTempFile("mitmproxy-python-plugin", ".py"); - try ( - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("scripts/proxy.py"); - FileOutputStream outputStream = new FileOutputStream(outfile)) { + try ( + InputStream inputStream = getClass().getClassLoader().getResourceAsStream("scripts/proxy.py"); + FileOutputStream outputStream = new FileOutputStream(outfile)) { - IOUtils.copy(inputStream, outputStream); - } + IOUtils.copy(inputStream, outputStream); + } - return outfile.getCanonicalPath(); + return outfile.getCanonicalPath(); + } + + public synchronized void stop() throws InterruptedException { + if (!isRunning.compareAndSet(true, false)) { + // Already stopped (or never started) — nothing to do. This makes + // stop() safe to call from a finally block / teardown hook even if + // start() failed or stop() was already called elsewhere. This is + // an expected, supported case (that's the point of making stop() + // idempotent), so INFO rather than WARN is the right severity — + // it's not something the caller needs to investigate. + log.info("stop() called but MitmproxyJava is not currently running; ignoring"); + return; } - public void stop() throws InterruptedException { - if (mitmproxyProcess != null) { - mitmproxyProcess.cancel(true); - } + try { + if (mitmproxyProcess != null) { + mitmproxyProcess.cancel(true); + } + if (server != null) { server.stop(1000); - waitForPortToBeFree(proxyPort); + } + waitForPortToBeFree(actualProxyPort); + } finally { + server = null; + mitmproxyProcess = null; + actualProxyPort = -1; } + } - @SneakyThrows - private void waitForPortToBeFree(int port) { - final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); - - while (true) { - if(timeoutTime.isBefore(LocalDateTime.now())){ - throw new TimeoutException("Timed out waiting for mitmproxy to stop"); - } - try (Socket s = new Socket(LOCALHOST_IP, port)) { - } catch (IOException ioe) { - return; - } - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } + @SneakyThrows + private void waitForPortToBeFree(int port) { + final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); + + while (true) { + if (timeoutTime.isBefore(LocalDateTime.now())) { + throw new TimeoutException("Timed out waiting for mitmproxy to stop"); + } + + if (!isPortInUse(port)) { + return; + } + + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + } + } + + private boolean isPortInUse(int port) { + // A successful connection means something is still listening (port busy). + // A connection failure (refused) means nothing's there (port free). + try (Socket s = new Socket(LOCALHOST_IP, port)) { + return true; + } catch (IOException ioe) { + return false; } + } - @SneakyThrows - private void waitForPortToBeInUse(int port) { - final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); - - while (true) { - - if(timeoutTime.isBefore(LocalDateTime.now())){ - throw new TimeoutException("Timed out waiting for mitmproxy to start"); - } - - try (Socket s = new Socket(LOCALHOST_IP, port)) { - return; - } catch (IOException ioe) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } + private void waitForPortToBeInUse(int port) throws TimeoutException { + final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); + + while (true) { + if (timeoutTime.isBefore(LocalDateTime.now())) { + throw new TimeoutException("Timed out waiting for mitmproxy to start"); + } + + if (isPortInUse(port)) { + return; + } + + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); } -} + } + +} \ No newline at end of file diff --git a/src/main/java/io/appium/mitmproxy/MitmproxyServer.java b/src/main/java/io/appium/mitmproxy/MitmproxyServer.java index c88e433..af60b95 100644 --- a/src/main/java/io/appium/mitmproxy/MitmproxyServer.java +++ b/src/main/java/io/appium/mitmproxy/MitmproxyServer.java @@ -11,63 +11,98 @@ import java.nio.ByteBuffer; import java.util.function.Function; +/** + * NOTE ON THREAD SAFETY: + * {@code java_websocket}'s {@link WebSocketServer} dispatches each connection's + * messages on its own thread, and mitmproxy can hold multiple concurrent + * connections open (one per in-flight HTTP transaction being proxied). That + * means {@link #onMessage(WebSocket, ByteBuffer)} — and therefore the + * user-supplied {@code interceptor} function — can be invoked concurrently + * from multiple threads at the same time. + * + * {@link MessageSerializer} and {@link InterceptedMessage} are stateless / + * per-call, so they're safe to share across those threads as-is. The + * remaining responsibility is on the caller: if your interceptor reads or + * writes shared state (e.g. collecting messages into a list), that state + * must be a thread-safe structure (e.g. {@code CopyOnWriteArrayList}, + * {@code Collections.synchronizedList}, or a {@code ConcurrentLinkedQueue}) — + * a plain {@code ArrayList} will corrupt under concurrent access. + */ public class MitmproxyServer extends WebSocketServer { - private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); - - private final Function interceptor; - - private final MessageSerializer messageSerializer; - - MitmproxyServer(InetSocketAddress address, Function interceptor) { - super(address); - this.interceptor = interceptor; - this.messageSerializer = new MessageSerializer(); - } - - @Override - public void onOpen(WebSocket conn, ClientHandshake handshake) { - LOGGER.debug("new connection to websocket server" + conn.getRemoteSocketAddress()); - } - - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - LOGGER.debug("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); - } - - @Override - public void onMessage(WebSocket conn, String message) { - LOGGER.debug("received message from " + conn.getRemoteSocketAddress() + " : " + message); - } - - @Override - public void onMessage(WebSocket conn, ByteBuffer rawInputMessage) { - InterceptedMessage incomingMessage = this.messageSerializer.deserializeMessage(rawInputMessage); - - InterceptedMessage modifiedMessage = interceptor.apply(incomingMessage); - - // if the supplied interceptor function does not return a message, assume no changes were intended and just - // complete the request - InterceptedMessage messageToSendBack = modifiedMessage; - - if (modifiedMessage == null) { - messageToSendBack = incomingMessage; - } - - try { - conn.send(this.messageSerializer.serializeMessage(messageToSendBack)); - } catch (JsonProcessingException e) { - LOGGER.error(e.getMessage()); - } - } - - @Override - public void onError(WebSocket conn, Exception ex) { - LOGGER.error("an error occured on connection " + conn.getRemoteSocketAddress() + ":" + ex); + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); + + private final Function interceptor; + + private final MessageSerializer messageSerializer; + + MitmproxyServer(InetSocketAddress address, Function interceptor) { + super(address); + this.interceptor = interceptor; + this.messageSerializer = new MessageSerializer(); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + LOGGER.debug("new connection to websocket server" + conn.getRemoteSocketAddress()); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + LOGGER.debug("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); + } + + @Override + public void onMessage(WebSocket conn, String message) { + LOGGER.debug("received message from " + conn.getRemoteSocketAddress() + " : " + message); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer rawInputMessage) { + // NOTE: this method runs concurrently on a per-connection thread — see + // the class-level Javadoc. deserializeMessage/serializeMessage are + // stateless so they're safe to call from multiple threads here; the + // interceptor call below is the boundary into user code. + InterceptedMessage incomingMessage = this.messageSerializer.deserializeMessage(rawInputMessage); + + InterceptedMessage messageToSendBack; + try { + InterceptedMessage modifiedMessage = interceptor.apply(incomingMessage); + + // if the supplied interceptor function does not return a message, assume no changes were intended and + // just complete the request + messageToSendBack = (modifiedMessage != null) ? modifiedMessage : incomingMessage; + } catch (RuntimeException e) { + // Without this, an exception thrown by user code (e.g. a + // non-thread-safe collection blowing up under concurrent access) + // propagates up through the websocket library's thread and the + // in-flight HTTP request mitmproxy is waiting on hangs forever + // with no indication of why. Log it and pass the request through + // unmodified instead of leaving the caller hanging. + LOGGER.error("interceptor threw an exception while processing " + incomingMessage.getRequest().getUrl() + + "; passing request through unmodified", e); + messageToSendBack = incomingMessage; } - @Override - public void onStart() { - LOGGER.info("websocket server started successfully"); + try { + conn.send(this.messageSerializer.serializeMessage(messageToSendBack)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage()); } -} + } + + @Override + public void onError(WebSocket conn, Exception ex) { + // conn is null for server-level fatal errors (e.g. a bind failure on + // startup) that happen before any client connection exists — calling + // conn.getRemoteSocketAddress() in that case throws an NPE that hides + // the actual underlying exception (ex) from the logs. + String connectionInfo = (conn != null) ? String.valueOf(conn.getRemoteSocketAddress()) : "N/A (no connection - likely a server-level error)"; + LOGGER.error("an error occured on connection " + connectionInfo + ":" + ex, ex); + } + + @Override + public void onStart() { + LOGGER.info("websocket server started successfully"); + } +} \ No newline at end of file diff --git a/src/main/resources/scripts/proxy.py b/src/main/resources/scripts/proxy.py index 01ab562..530034f 100644 --- a/src/main/resources/scripts/proxy.py +++ b/src/main/resources/scripts/proxy.py @@ -57,9 +57,15 @@ def __init__(self): self.queue = queue.Queue() self.intercept_paths = frozenset([]) self.only_intercept_text_files = False + self.ws_port = 8765 self.finished = False - # Start websocket thread - threading.Thread(target=self.websocket_thread).start() + self._websocket_thread_started = False + # NOTE: the websocket thread is intentionally NOT started here. mitmproxy + # calls __init__() before load()/configure(), so starting the thread here + # would race against configure() setting the real wsPort from the CLI — + # the background thread could connect using the stale default before the + # real port is applied. It's started instead on the first configure() + # call, once all options (including wsPort) are guaranteed to be set. def load(self, loader): loader.add_option( @@ -75,6 +81,16 @@ def load(self, loader): default = False, help = "If true, the plugin only intercepts text files and passes through other types of files", ) + loader.add_option( + name = "wsPort", + typespec = int, + default = 8765, + help = "The port this script's websocket client connects to, to talk back to the Java control process. " + "Must match the port MitmproxyJava bound its control-channel server to. Defaults to 8765 for " + "backwards compatibility, but MUST be set explicitly to a unique free port whenever more than " + "one MitmproxyJava instance may run on the same machine at once (e.g. parallel test execution) " + "— otherwise multiple instances will collide trying to use the same fixed port.", + ) return def configure(self, updates): @@ -86,6 +102,12 @@ def configure(self, updates): self.only_intercept_text_files = ctx.options.onlyInterceptTextFiles #print("Only intercept text files:") #print(self.only_intercept_text_files) + if "wsPort" in updates: + self.ws_port = ctx.options.wsPort + + if not self._websocket_thread_started: + self._websocket_thread_started = True + threading.Thread(target=self.websocket_thread).start() return def send_message(self, metadata, data1, data2): @@ -220,7 +242,7 @@ async def websocket_loop(self): """ while not self.finished: try: - async with websockets.connect('ws://localhost:8765', max_size = None) as websocket: + async with websockets.connect(f'ws://localhost:{self.ws_port}', max_size = None) as websocket: while True: # Make sure connection is still live. await websocket.ping() @@ -253,4 +275,4 @@ async def websocket_loop(self): addons = [ WebSocketAdapter() -] +] \ No newline at end of file diff --git a/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java b/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java new file mode 100644 index 0000000..9dd3ef7 --- /dev/null +++ b/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java @@ -0,0 +1,226 @@ +package io.appium.mitmproxy; + +import kong.unirest.core.Unirest; +import org.junit.After; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests specifically exercising the thread-safety fixes made to + * {@link MitmproxyJava} and {@link MitmproxyServer}: + *

+ * 1. Many requests in flight at once should all be captured correctly by a + * thread-safe interceptor, with no lost/corrupted messages. + * 2. A racing start() from multiple threads should let exactly one caller + * succeed and everyone else should fail fast with IllegalStateException, + * rather than double-starting the websocket server / mitmdump process. + * 3. An interceptor that throws should not hang the in-flight HTTP request — + * the request should still complete (unmodified) instead of timing out. + * 4. stop() without a successful start() should be a safe no-op, not an NPE. + *

+ * Requires mitmdump to be resolvable — see MitmproxyJavaTest.getMITMDumpPath(). + */ +public class ConcurrencyTest { + + private MitmproxyJava proxy; + + @After + public void tearDown() throws InterruptedException { + if (proxy != null) { + proxy.stop(); // safe no-op now even if start() never succeeded + } + // Unirest 4's Config locks itself once the underlying HTTP client is + // built (i.e. after the first request), so the *next* test's call to + // Unirest.config().proxy(...) would throw UnirestConfigException + // without this reset(). reset() also clears default headers, proxy, + // etc., so there's no separate clearDefaultHeaders() call needed. + Unirest.config().reset(); + } + + @Test + public void concurrentRequestsAreAllCapturedWithoutLossOrCorruption() throws Exception { + int requestCount = 25; + int port = 8090; + + // The collection under test: this MUST be thread-safe, since the + // interceptor is invoked concurrently once more than one request is + // in flight. This is exactly the pattern from ThreadSafeMessageCollector. + List messages = new CopyOnWriteArrayList<>(); + + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> { + messages.add(m); + return m; + }, port, null); + proxy.start(); + + Unirest.config().proxy("localhost", port); + + ExecutorService pool = Executors.newFixedThreadPool(10); + try { + List> futures = IntStream.range(0, requestCount) + .mapToObj(i -> pool.submit(() -> { + try { + Unirest.get("http://appium.io") + .header("requestIndex", String.valueOf(i)) + .asString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })) + .collect(Collectors.toList()); + + for (Future f : futures) { + f.get(15, TimeUnit.SECONDS); // fail the test if any request hangs + } + } finally { + pool.shutdown(); + } + + proxy.stop(); + + // No lost or duplicated messages despite concurrent writers. + assertThat(messages).hasSize(requestCount); + + // Every request index shows up exactly once — if the underlying list + // were a plain ArrayList, concurrent adds could silently drop entries + // or throw ConcurrentModificationException before we even get here. + List seenIndices = messages.stream() + .map(m -> headerValue(m, "requestIndex")) + .collect(Collectors.toList()); + List expectedIndices = IntStream.range(0, requestCount) + .mapToObj(String::valueOf) + .collect(Collectors.toList()); + assertThat(seenIndices).containsExactlyInAnyOrderElementsOf(expectedIndices); + } + + @Test + public void onlyOneConcurrentStartCallSucceeds() throws Exception { + int port = 8091; + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, port, null); + + int threadCount = 5; + CountDownLatch readyLatch = new CountDownLatch(threadCount); + CountDownLatch goLatch = new CountDownLatch(1); + AtomicInteger successCount = new AtomicInteger(0); + AtomicInteger failureCount = new AtomicInteger(0); + + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + try { + for (int i = 0; i < threadCount; i++) { + pool.submit(() -> { + readyLatch.countDown(); + try { + goLatch.await(); // line every thread up to hit start() at once + proxy.start(); + successCount.incrementAndGet(); + } catch (IllegalStateException e) { + failureCount.incrementAndGet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + readyLatch.await(5, TimeUnit.SECONDS); + goLatch.countDown(); // release all threads simultaneously + + pool.shutdown(); + assertThat(pool.awaitTermination(15, TimeUnit.SECONDS)).isTrue(); + } finally { + if (!pool.isShutdown()) { + pool.shutdownNow(); + } + } + + // synchronized + AtomicBoolean guard means exactly one thread wins the race. + assertThat(successCount.get()).isEqualTo(1); + assertThat(failureCount.get()).isEqualTo(threadCount - 1); + + // and the proxy is actually usable after the race settles + Unirest.config().proxy("localhost", port); + assertThat(Unirest.get("http://appium.io").asString().getStatus()).isEqualTo(200); + } + + @Test + public void interceptorExceptionDoesNotHangTheRequest() throws Exception { + int port = 8092; + + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> { + throw new RuntimeException("boom - simulated broken interceptor"); + }, port, null); + proxy.start(); + + Unirest.config().proxy("localhost", port); + + // Before the try/catch fix in MitmproxyServer.onMessage, this would + // hang indefinitely because the connection thread died with an + // uncaught exception and never sent a response back to mitmdump. + ExecutorService pool = Executors.newSingleThreadExecutor(); + try { + Future future = pool.submit(() -> + Unirest.get("http://appium.io").asString().getStatus()); + + Integer status = future.get(10, TimeUnit.SECONDS); // throws TimeoutException if it hangs + assertThat(status).isEqualTo(200); + } finally { + pool.shutdownNow(); + } + } + + @Test + public void stopWithoutStartIsASafeNoOp() throws InterruptedException { + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8093, null); + // start() deliberately not called + proxy.stop(); // should not throw + } + + @Test + public void stopCanBeCalledMultipleTimesSafely() throws Exception { + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8094, null); + proxy.start(); + proxy.stop(); + proxy.stop(); // second call should be a no-op, not throw + } + + @Test + public void startAfterStopWorksAgain() throws Exception { + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8095, null); + proxy.start(); + proxy.stop(); + + // instance should be reusable, not permanently wedged + proxy.start(); + Unirest.config().proxy("localhost", 8095); + assertThat(Unirest.get("http://appium.io").asString().getStatus()).isEqualTo(200); + } + + @Test + public void startTwiceWithoutStopThrows() throws Exception { + proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8096, null); + proxy.start(); + + assertThatThrownBy(() -> proxy.start()) + .isInstanceOf(IllegalStateException.class); + } + + private static String headerValue(InterceptedMessage message, String headerName) { + return message.getRequest().getHeaders().stream() + .filter(h -> h.length == 2 && headerName.equalsIgnoreCase(h[0])) + .map(h -> h[1]) + .findFirst() + .orElse(null); + } +} \ No newline at end of file diff --git a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java index 85ea9c3..4386550 100644 --- a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java +++ b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java @@ -1,9 +1,8 @@ package io.appium.mitmproxy; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; -import org.apache.http.HttpHost; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; import org.junit.Test; import java.io.IOException; @@ -18,117 +17,115 @@ public class MitmproxyJavaTest { - //private static final String MITMDUMP_PATH = "C:\\Python37\\Scripts\\mitmdump.exe"; - private static final String MITMDUMP_PATH = getMITMDumpPath(); - - static String getMITMDumpPath(){ - if(System.getProperty("os.name").contains("Mac")){ - return "/opt/homebrew/bin/mitmdump"; - } - return "/usr/local/bin/mitmdump"; - } - @Test - public void ConstructorTest() throws InterruptedException, IOException, TimeoutException { - MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { - System.out.println(m.getRequest().getUrl()); - return m; - }); - proxy.start(); - System.out.println("advanced in test"); - proxy.stop(); - } - - @Test - public void SimpleTest() throws InterruptedException, TimeoutException, IOException, UnirestException { - List messages = new ArrayList<>(); - - MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { - messages.add(m); - return m; - }); - proxy.start(); - - Unirest.setProxy(new HttpHost("localhost", 8080)); - Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); - - proxy.stop(); - final InterceptedMessage firstMessage = messages.get(0); - - assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); - assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); - assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); - } - - @Test - public void NullInterceptorReturnTest() throws InterruptedException, TimeoutException, IOException, UnirestException { - List messages = new ArrayList<>(); - - MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { - messages.add(m); - return null; - }, 8087, null); - proxy.start(); - - Unirest.setProxy(new HttpHost("localhost", 8087)); - Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); - - proxy.stop(); - - assertThat(messages).isNotEmpty(); - - final InterceptedMessage firstMessage = messages.get(0); - - assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); - assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); - assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); - } - - @Test - public void ResponseModificationTest() throws InterruptedException, TimeoutException, IOException, UnirestException { - List messages = new ArrayList<>(); - - MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { - messages.add(m); - m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); - m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); - m.getResponse().setStatusCode(208); - return m; - }); - proxy.start(); - - Unirest.setProxy(new HttpHost("localhost", 8080)); - HttpResponse response = Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); - proxy.stop(); - - assertThat(response.getBody()).isEqualTo("Hi from Test"); - - final InterceptedMessage firstMessage = messages.get(0); - - assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); - assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); - assertThat(firstMessage.getResponse().getHeaders()).containsOnlyOnce(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); - assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(208); - } - - @Test - public void shouldAddParametersToMitmdumpStart() throws IOException, TimeoutException, InterruptedException { - List mitmdumpParams = new ArrayList<>(); - mitmdumpParams.add("testParam"); - - List spiedParams = spy(mitmdumpParams); - - MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { - m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); - m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); - m.getResponse().setStatusCode(208); - return m; - }, 8087, spiedParams); - - proxy.start(); - proxy.stop(); - - //to verify that additional params were actually included to start path - verify(spiedParams).toArray(); - - } + //private static final String MITMDUMP_PATH = "C:\\Python37\\Scripts\\mitmdump.exe"; + private static final String MITMDUMP_PATH = getMITMDumpPath(); + + static String getMITMDumpPath() { + return "/Library/Frameworks/Python.framework/Versions/3.11/bin/mitmdump"; + } + + @Test + public void ConstructorTest() throws InterruptedException, IOException, TimeoutException { + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + System.out.println(m.getRequest().getUrl()); + return m; + }); + proxy.start(); + System.out.println("advanced in test"); + proxy.stop(); + } + + @Test + public void SimpleTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); + + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + messages.add(m); + return m; + }); + proxy.start(); + + Unirest.config().reset().proxy("localhost", 8080); + Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); + + proxy.stop(); + final InterceptedMessage firstMessage = messages.get(0); + + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); + } + + @Test + public void NullInterceptorReturnTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); + + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + messages.add(m); + return null; + }, 8087, null); + proxy.start(); + + Unirest.config().reset().proxy("localhost", 8087); + Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); + + proxy.stop(); + + assertThat(messages).isNotEmpty(); + + final InterceptedMessage firstMessage = messages.get(0); + + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); + } + + @Test + public void ResponseModificationTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); + + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + messages.add(m); + m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); + m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + m.getResponse().setStatusCode(208); + return m; + }); + proxy.start(); + + Unirest.config().reset().proxy("localhost", 8080); + HttpResponse response = Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); + proxy.stop(); + + assertThat(response.getBody()).isEqualTo("Hi from Test"); + + final InterceptedMessage firstMessage = messages.get(0); + + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getHeaders()).containsOnlyOnce(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(208); + } + + @Test + public void shouldAddParametersToMitmdumpStart() throws IOException, TimeoutException, InterruptedException { + List mitmdumpParams = new ArrayList<>(); + mitmdumpParams.add("testParam"); + + List spiedParams = spy(mitmdumpParams); + + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); + m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + m.getResponse().setStatusCode(208); + return m; + }, 8087, spiedParams); + + proxy.start(); + proxy.stop(); + + //to verify that additional params were actually included to start path + verify(spiedParams).toArray(); + + } } \ No newline at end of file