From 9cc824cb5dcb648afe0598c58d6f898a2cb1848e Mon Sep 17 00:00:00 2001 From: Quacky Date: Mon, 7 Aug 2017 17:43:21 -0500 Subject: [PATCH] add in portmapper source U P N P BABY --- .../org.eclipse.core.resources.prefs | 2 + UPnP/.settings/org.eclipse.jdt.ui.prefs | 131 ++++++ UPnP/.settings/org.eclipse.m2e.core.prefs | 4 + UPnP/build.bat | 2 + UPnP/build.gradle | 92 +++++ UPnP/config/Build PortMapper.launch | 11 + UPnP/config/PortMapper.launch | 15 + UPnP/gradle/defaultEclipseJdtPrefs.properties | 313 ++++++++++++++ UPnP/gradle/license-header.txt | 15 + UPnP/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54706 bytes UPnP/gradle/wrapper/gradle-wrapper.properties | 6 + UPnP/gradlew | 172 ++++++++ UPnP/gradlew.bat | 84 ++++ UPnP/lib/sbbi-upnplib-1.0.4.jar | Bin 0 -> 161962 bytes .../portmapper/CommandLineArguments.java | 118 ++++++ .../java/org/chris/portmapper/MacSetup.java | 32 ++ .../org/chris/portmapper/PortMapperApp.java | 389 ++++++++++++++++++ .../org/chris/portmapper/PortMapperCli.java | 238 +++++++++++ .../chris/portmapper/PortMapperStarter.java | 43 ++ .../java/org/chris/portmapper/Settings.java | 112 +++++ .../logging/LogMessageListener.java | 36 ++ .../logging/LogMessageOutputStream.java | 104 +++++ .../portmapper/logging/LogMessageWriter.java | 100 +++++ .../logging/LogbackConfiguration.java | 83 ++++ .../chris/portmapper/model/PortMapping.java | 159 +++++++ .../portmapper/model/PortMappingPreset.java | 173 ++++++++ .../org/chris/portmapper/model/Protocol.java | 50 +++ .../portmapper/model/SinglePortMapping.java | 74 ++++ .../portmapper/router/AbstractRouter.java | 120 ++++++ .../router/AbstractRouterFactory.java | 91 ++++ .../org/chris/portmapper/router/IRouter.java | 121 ++++++ .../portmapper/router/RouterException.java | 31 ++ .../cling/ClingOperationFailedException.java | 36 ++ .../cling/ClingPortMappingExtractor.java | 154 +++++++ .../router/cling/ClingRegistryListener.java | 110 +++++ .../portmapper/router/cling/ClingRouter.java | 145 +++++++ .../router/cling/ClingRouterException.java | 34 ++ .../router/cling/ClingRouterFactory.java | 89 ++++ .../cling/action/AbstractClingAction.java | 83 ++++ .../router/cling/action/ActionService.java | 60 +++ .../cling/action/AddPortMappingAction.java | 58 +++ .../router/cling/action/ClingAction.java | 28 ++ .../cling/action/DeletePortMappingAction.java | 54 +++ .../cling/action/GetExternalIpAction.java | 36 ++ .../action/GetPortMappingEntryAction.java | 75 ++++ .../portmapper/router/dummy/DummyRouter.java | 110 +++++ .../router/dummy/DummyRouterFactory.java | 52 +++ .../router/sbbi/SBBIPortMappingExtractor.java | 182 ++++++++ .../portmapper/router/sbbi/SBBIRouter.java | 233 +++++++++++ .../router/sbbi/SBBIRouterFactory.java | 76 ++++ .../router/weupnp/WeUPnPRouter.java | 182 ++++++++ .../router/weupnp/WeUPnPRouterFactory.java | 92 +++++ .../portmapper/util/EncodingUtilities.java | 61 +++ UPnP/src/main/resources/logback.xml | 32 ++ .../resources/ConnectTask_de.properties | 26 ++ .../resources/ConnectTask_en.properties | 26 ++ .../resources/ConnectTask_nb.properties | 26 ++ .../resources/PortMapperApp_de.properties | 231 +++++++++++ .../resources/PortMapperApp_en.properties | 230 +++++++++++ .../resources/PortMapperApp_nb.properties | 177 ++++++++ .../router/sbbi/TestPortMappingExtractor.java | 138 +++++++ 61 files changed, 5757 insertions(+) create mode 100644 UPnP/.settings/org.eclipse.core.resources.prefs create mode 100644 UPnP/.settings/org.eclipse.jdt.ui.prefs create mode 100644 UPnP/.settings/org.eclipse.m2e.core.prefs create mode 100644 UPnP/build.bat create mode 100644 UPnP/build.gradle create mode 100644 UPnP/config/Build PortMapper.launch create mode 100644 UPnP/config/PortMapper.launch create mode 100644 UPnP/gradle/defaultEclipseJdtPrefs.properties create mode 100644 UPnP/gradle/license-header.txt create mode 100644 UPnP/gradle/wrapper/gradle-wrapper.jar create mode 100644 UPnP/gradle/wrapper/gradle-wrapper.properties create mode 100644 UPnP/gradlew create mode 100644 UPnP/gradlew.bat create mode 100644 UPnP/lib/sbbi-upnplib-1.0.4.jar create mode 100644 UPnP/src/main/java/org/chris/portmapper/CommandLineArguments.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/MacSetup.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/PortMapperApp.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/PortMapperCli.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/PortMapperStarter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/Settings.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/logging/LogMessageListener.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/logging/LogMessageOutputStream.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/logging/LogMessageWriter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/logging/LogbackConfiguration.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/model/PortMapping.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/model/PortMappingPreset.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/model/Protocol.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/model/SinglePortMapping.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/AbstractRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/AbstractRouterFactory.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/IRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/RouterException.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingOperationFailedException.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingPortMappingExtractor.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRegistryListener.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterException.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterFactory.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/AbstractClingAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/ActionService.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/AddPortMappingAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/ClingAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/DeletePortMappingAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetExternalIpAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetPortMappingEntryAction.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouterFactory.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIPortMappingExtractor.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouterFactory.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouter.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouterFactory.java create mode 100644 UPnP/src/main/java/org/chris/portmapper/util/EncodingUtilities.java create mode 100644 UPnP/src/main/resources/logback.xml create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_de.properties create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_en.properties create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_nb.properties create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_de.properties create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_en.properties create mode 100644 UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_nb.properties create mode 100644 UPnP/src/test/java/org/chris/portmapper/router/sbbi/TestPortMappingExtractor.java diff --git a/UPnP/.settings/org.eclipse.core.resources.prefs b/UPnP/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/UPnP/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/UPnP/.settings/org.eclipse.jdt.ui.prefs b/UPnP/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..000a7d3 --- /dev/null +++ b/UPnP/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,131 @@ +cleanup.add_default_serial_version_id=true +cleanup.add_generated_serial_version_id=false +cleanup.add_missing_annotations=true +cleanup.add_missing_deprecated_annotations=true +cleanup.add_missing_methods=false +cleanup.add_missing_nls_tags=false +cleanup.add_missing_override_annotations=true +cleanup.add_missing_override_annotations_interface_methods=true +cleanup.add_serial_version_id=false +cleanup.always_use_blocks=true +cleanup.always_use_parentheses_in_expressions=false +cleanup.always_use_this_for_non_static_field_access=false +cleanup.always_use_this_for_non_static_method_access=false +cleanup.convert_functional_interfaces=false +cleanup.convert_to_enhanced_for_loop=false +cleanup.correct_indentation=false +cleanup.format_source_code=false +cleanup.format_source_code_changes_only=false +cleanup.insert_inferred_type_arguments=false +cleanup.make_local_variable_final=true +cleanup.make_parameters_final=false +cleanup.make_private_fields_final=true +cleanup.make_type_abstract_if_missing_method=false +cleanup.make_variable_declarations_final=false +cleanup.never_use_blocks=false +cleanup.never_use_parentheses_in_expressions=true +cleanup.organize_imports=false +cleanup.qualify_static_field_accesses_with_declaring_class=false +cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +cleanup.qualify_static_member_accesses_with_declaring_class=true +cleanup.qualify_static_method_accesses_with_declaring_class=false +cleanup.remove_private_constructors=true +cleanup.remove_redundant_type_arguments=true +cleanup.remove_trailing_whitespaces=false +cleanup.remove_trailing_whitespaces_all=true +cleanup.remove_trailing_whitespaces_ignore_empty=false +cleanup.remove_unnecessary_casts=true +cleanup.remove_unnecessary_nls_tags=true +cleanup.remove_unused_imports=true +cleanup.remove_unused_local_variables=false +cleanup.remove_unused_private_fields=true +cleanup.remove_unused_private_members=false +cleanup.remove_unused_private_methods=true +cleanup.remove_unused_private_types=true +cleanup.sort_members=false +cleanup.sort_members_all=false +cleanup.use_anonymous_class_creation=false +cleanup.use_blocks=false +cleanup.use_blocks_only_for_return_and_throw=false +cleanup.use_lambda=true +cleanup.use_parentheses_in_expressions=false +cleanup.use_this_for_non_static_field_access=false +cleanup.use_this_for_non_static_field_access_only_if_necessary=true +cleanup.use_this_for_non_static_method_access=false +cleanup.use_this_for_non_static_method_access_only_if_necessary=true +cleanup.use_type_arguments=false +cleanup_profile=org.eclipse.jdt.ui.default.eclipse_clean_up_profile +cleanup_settings_version=2 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_PortMapper Formatter +formatter_settings_version=12 +org.eclipse.jdt.ui.exception.name=e +org.eclipse.jdt.ui.gettersetter.use.is=true +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com;\#org.mockito.Mockito; +org.eclipse.jdt.ui.javadoc=false +org.eclipse.jdt.ui.keywordthis=false +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.overrideannotation=true +org.eclipse.jdt.ui.staticondemandthreshold=1 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=true +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_cleanup.use_type_arguments=false diff --git a/UPnP/.settings/org.eclipse.m2e.core.prefs b/UPnP/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/UPnP/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/UPnP/build.bat b/UPnP/build.bat new file mode 100644 index 0000000..a19744c --- /dev/null +++ b/UPnP/build.bat @@ -0,0 +1,2 @@ +./gradlew build +java -jar build/libs/portmapper-*.jar \ No newline at end of file diff --git a/UPnP/build.gradle b/UPnP/build.gradle new file mode 100644 index 0000000..bd48452 --- /dev/null +++ b/UPnP/build.gradle @@ -0,0 +1,92 @@ +plugins { + id 'java' + id 'eclipse' + id "com.github.hierynomus.license" version "0.13.1" +} + +repositories { + mavenLocal() + maven { url 'http://4thline.org/m2' } + jcenter() + flatDir { dirs 'lib' } +} + +version = '2.0.0' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:all' + options.encoding = 'UTF-8' +} + +test { + if(logger.infoEnabled) { + testLogging.showStandardStreams = true + } + jvmArgs '-XX:+HeapDumpOnOutOfMemoryError', '-enableassertions' +} + +processResources { + rename(/(\w+)_en.properties/, '$1.properties') +} + +task replaceVersionTokenInTranslations { + doLast { + ant.replace(dir: 'build/resources/main', encoding: 'ISO-8859-1') { + include(name: '**/*.properties') + replacefilter(token: '@VERSION_NUMBER@', value: project.version) + } + } +} + +jar.dependsOn(replaceVersionTokenInTranslations) + +jar { + from { + configurations.compile.collect { + it.isDirectory() ? it : zipTree(it) + } + configurations.runtime.collect { + it.isDirectory() ? it : zipTree(it) + } + } + manifest { attributes 'Main-Class': 'org.chris.portmapper.PortMapperStarter' } +} + +dependencies { + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:2.1.0' + + compile 'args4j:args4j:2.33' + compile 'org.slf4j:slf4j-api:1.7.22' + compile 'ch.qos.logback:logback-classic:1.1.8' + compile 'com.miglayout:miglayout-swing:5.0' + compile 'org.jdesktop.bsaf:bsaf:1.9.2' + compile ':sbbi-upnplib:1.0.4' + compile 'org.fourthline.cling:cling-support:2.1.1' + compile 'org.bitlet:weupnp:0.1.4' + + runtime 'commons-jxpath:commons-jxpath:1.1' // sbbi + compile 'org.slf4j:jul-to-slf4j:1.7.22' +} + +license { + header = file('gradle/license-header.txt') + ext.year = 2015 + ext.name = 'Christoph Pirkl' + ext.email = 'christoph at users.sourceforge.net' +} + +eclipse { + classpath { + downloadSources = true + } + jdt.file { + beforeMerged { jdt -> + File defaultProperties = new File("${rootProject.projectDir}/gradle/defaultEclipseJdtPrefs.properties").absoluteFile + logger.info "Load defaults from $defaultProperties for $project" + jdt.load(defaultProperties) + } + } +} \ No newline at end of file diff --git a/UPnP/config/Build PortMapper.launch b/UPnP/config/Build PortMapper.launch new file mode 100644 index 0000000..fef1950 --- /dev/null +++ b/UPnP/config/Build PortMapper.launch @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/UPnP/config/PortMapper.launch b/UPnP/config/PortMapper.launch new file mode 100644 index 0000000..0b1d412 --- /dev/null +++ b/UPnP/config/PortMapper.launch @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/UPnP/gradle/defaultEclipseJdtPrefs.properties b/UPnP/gradle/defaultEclipseJdtPrefs.properties new file mode 100644 index 0000000..8e57ddb --- /dev/null +++ b/UPnP/gradle/defaultEclipseJdtPrefs.properties @@ -0,0 +1,313 @@ +# Template for org.eclipse.jdt.core.prefs, used by build.gradle + +# The following settings will be overwritten by gradle: +#eclipse.preferences.version=1 +#org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +#org.eclipse.jdt.core.compiler.compliance=1.8 +#org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +#org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +#org.eclipse.jdt.core.compiler.source=1.8 + +# Formatter settings: +eclipse.preferences.version=1 +org.eclipse.jdt.core.codeComplete.argumentPrefixes= +org.eclipse.jdt.core.codeComplete.argumentSuffixes= +org.eclipse.jdt.core.codeComplete.fieldPrefixes= +org.eclipse.jdt.core.codeComplete.fieldSuffixes= +org.eclipse.jdt.core.codeComplete.localPrefixes= +org.eclipse.jdt.core.codeComplete.localSuffixes= +org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=120 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=false +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true + diff --git a/UPnP/gradle/license-header.txt b/UPnP/gradle/license-header.txt new file mode 100644 index 0000000..4f6175b --- /dev/null +++ b/UPnP/gradle/license-header.txt @@ -0,0 +1,15 @@ +UPnP PortMapper - A tool for managing port forwardings via UPnP +Copyright (C) ${year} ${name} <${email}> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/UPnP/gradle/wrapper/gradle-wrapper.jar b/UPnP/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..605c807131a4669bd82fb98c607dee0b96695cec GIT binary patch literal 54706 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 literal 0 HcmV?d00001 diff --git a/UPnP/gradle/wrapper/gradle-wrapper.properties b/UPnP/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b3e3cd --- /dev/null +++ b/UPnP/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Jun 18 20:27:21 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip diff --git a/UPnP/gradlew b/UPnP/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/UPnP/gradlew @@ -0,0 +1,172 @@ +#!/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/UPnP/gradlew.bat b/UPnP/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/UPnP/gradlew.bat @@ -0,0 +1,84 @@ +@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/UPnP/lib/sbbi-upnplib-1.0.4.jar b/UPnP/lib/sbbi-upnplib-1.0.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..271e919c7e82b9fa7fc4edb2de0f84f0e3381ebe GIT binary patch literal 161962 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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +import org.chris.portmapper.model.Protocol; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.ParserProperties; + +import static java.util.Arrays.*; + +public class CommandLineArguments { + + @Option(name = "-add", usage = "add pf", depends = { "-internalPort", "-externalPort", + "-protocol" }, forbids = { "-gui", "-delete", "-info", "-list" }) + private boolean addPortMapping; + @Option(name = "-delete", usage = "delete pf", depends = { "-externalPort", + "-protocol" }, forbids = { "-gui", "-add", "-info", "-list" }) + private boolean deletePortMapping; + @Option(name = "-info", usage = "router info", forbids = { "-gui", "-delete", "-add", "-list" }) + private boolean printInfo; + @Option(name = "-list", usage = "print pf", forbids = { "-gui", "-delete", "-info", "-add" }) + private boolean listPortMappings; + + @Option(name = "-ip", usage = "Internal IP of the port mapping (default: localhost)") + private String internalIp; + @Option(name = "-internalPort", usage = "Internal port of the port mapping") + private int internalPort; + @Option(name = "-externalPort", usage = "External port of the port mapping") + private int externalPort; + @Option(name = "-protocol", usage = "Protocol of the port mapping") + private Protocol protocol; + @Option(name = "-description", usage = "Description of the port mapping") + private String description; + + @Option(name = "-lib", usage = "UPnP library") + private String upnpLib; + + @Option(name = "-routerIndex", usage = "router index") + private Integer routerIndex; + + private final CmdLineParser parser; + + public CommandLineArguments() { + parser = new CmdLineParser(this, ParserProperties.defaults().withShowDefaults(true).withUsageWidth(80)); + } + + public boolean parse(final String[] args) { + try { + parser.parseArgument(asList(args)); + return true; + } catch (final CmdLineException e) { + System.err.println(e.getMessage()); + printHelp(); + return false; + } + } + + public boolean isAddPortMapping() { + return addPortMapping; + } + + public boolean isDeletePortMapping() { + return deletePortMapping; + } + + public boolean isPrintInfo() { + return printInfo; + } + + public boolean isListPortMappings() { + return listPortMappings; + } + + public String getInternalIp() { + return internalIp; + } + + public int getInternalPort() { + return internalPort; + } + + public int getExternalPort() { + return externalPort; + } + + public Protocol getProtocol() { + return protocol; + } + + public String getUpnpLib() { + return upnpLib; + } + + public Integer getRouterIndex() { + return routerIndex; + } + + public String getDescription() { + return description; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/MacSetup.java b/UPnP/src/main/java/org/chris/portmapper/MacSetup.java new file mode 100644 index 0000000..1b96a42 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/MacSetup.java @@ -0,0 +1,32 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +/** + * This class contains mac specific settings for the application name and the application menu. + */ +public class MacSetup { + + public static void setupMac() { + System.setProperty("com.apple.mrj.application.apple.menu.about.name", "UPnP PortMapper"); + System.setProperty("apple.laf.useScreenMenuBar", "true"); + System.setProperty("apple.awt.brushMetalLook", "false"); + System.setProperty("com.apple.mrj.application.growbox.intrudes", "false"); + System.setProperty("com.apple.mrj.application.live-resize", "true"); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/PortMapperApp.java b/UPnP/src/main/java/org/chris/portmapper/PortMapperApp.java new file mode 100644 index 0000000..f806099 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/PortMapperApp.java @@ -0,0 +1,389 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collection; +import java.util.Collections; +import java.util.EventObject; +import java.util.List; + +import javax.swing.JOptionPane; + +import org.chris.portmapper.gui.PortMapperView; +import org.chris.portmapper.logging.LogMessageListener; +import org.chris.portmapper.logging.LogMessageOutputStream; +import org.chris.portmapper.logging.LogbackConfiguration; +import org.chris.portmapper.model.PortMappingPreset; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; +import org.jdesktop.application.ResourceMap; +import org.jdesktop.application.SingleFrameApplication; +import org.jdesktop.application.utils.AppHelper; +import org.jdesktop.application.utils.OSXAdapter; +import org.jdesktop.application.utils.PlatformType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The main application class + */ +public class PortMapperApp extends SingleFrameApplication { + + /** + * The name of the system property which will be used as the directory where all configuration files will be stored. + */ + private static final String CONFIG_DIR_PROPERTY_NAME = "portmapper.config.dir"; + + /** + * The file name for the settings file. + */ + private static final String SETTINGS_FILENAME = "settings.xml"; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private IRouter router; + private Settings settings; + private final LogMessageOutputStream logMessageOutputStream = new LogMessageOutputStream(); + private final LogbackConfiguration logbackConfig = new LogbackConfiguration(); + + @Override + protected void startup() { + logbackConfig.registerOutputStream(logMessageOutputStream); + + setCustomConfigDir(); + + loadSettings(); + + final PortMapperView view = new PortMapperView(this); + addExitListener(new ExitListener() { + @Override + public boolean canExit(final EventObject arg0) { + return true; + } + + @Override + public void willExit(final EventObject arg0) { + disconnectRouter(); + } + }); + + show(view); + + if (AppHelper.getPlatform() == PlatformType.OS_X) { + registerMacOSXListeners(); + } + } + + private void registerMacOSXListeners() { + final PortMapperView view = getView(); + OSXAdapter.setPreferencesHandler(view, getMethod(PortMapperView.class, "changeSettings")); + OSXAdapter.setAboutHandler(view, getMethod(PortMapperView.class, "showAboutDialog")); + } + + private static Method getMethod(final Class clazz, final String name, final Class... parameterTypes) { + try { + return clazz.getMethod(name, parameterTypes); + } catch (final SecurityException e) { + throw new RuntimeException("Error getting method " + name + " of class " + clazz.getName(), e); + } catch (final NoSuchMethodException e) { + throw new RuntimeException("Error getting method " + name + " of class " + clazz.getName(), e); + } + } + + /** + * Read the system property with name {@link PortMapperApp#CONFIG_DIR_PROPERTY_NAME} and change the local storage + * directory if the property is given and points to a writable directory. If there is a directory named + * PortMapperConf in the current directory, use this as the configuration directory. + */ + private void setCustomConfigDir() { + final String customConfigurationDir = System.getProperty(CONFIG_DIR_PROPERTY_NAME); + final File portableAppConfigDir = new File("PortMapperConf"); + + // the property is set: check, if the given directory can be used + if (customConfigurationDir != null) { + final File dir = new File(customConfigurationDir); + if (!dir.isDirectory()) { + logger.error("Custom configuration directory '{}' is not a directory.", customConfigurationDir); + System.exit(1); + } + if (!dir.canRead() || !dir.canWrite()) { + logger.error("Can not read or write to custom configuration directory '{}'.", customConfigurationDir); + System.exit(1); + } + logger.info("Using custom configuration directory '{}'.", dir.getAbsolutePath()); + getContext().getLocalStorage().setDirectory(dir); + + // check, if the portable app directory exists and use this one + } else if (portableAppConfigDir.isDirectory() && portableAppConfigDir.canRead() + && portableAppConfigDir.canWrite()) { + logger.info("Found portable app configuration directory '{}'.", portableAppConfigDir.getAbsolutePath()); + getContext().getLocalStorage().setDirectory(portableAppConfigDir); + + // use the default configuration directory + } else { + logger.info("Using default configuration directory '{}'.", + getContext().getLocalStorage().getDirectory().getAbsolutePath()); + } + } + + /** + * Load the application settings from file {@link PortMapperApp#SETTINGS_FILENAME} located in the configuration + * directory. + */ + private void loadSettings() { + logger.debug("Loading settings from file {}", SETTINGS_FILENAME); + try { + settings = (Settings) getContext().getLocalStorage().load(SETTINGS_FILENAME); + } catch (final IOException e) { + logger.warn("Could not load settings from file " + SETTINGS_FILENAME, e); + } + + if (settings == null) { + logger.debug("Settings were not loaded from file {}: create new settings", SETTINGS_FILENAME); + settings = new Settings(); + } else { + logger.debug("Got settings {}", settings); + this.setLogLevel(settings.getLogLevel()); + } + } + + public void setLogMessageListener(final LogMessageListener listener) { + this.logMessageOutputStream.registerListener(listener); + } + + @Override + protected void shutdown() { + super.shutdown(); + logger.debug("Saving settings {} to file {}", settings, SETTINGS_FILENAME); + if (logger.isTraceEnabled()) { + for (final PortMappingPreset preset : settings.getPresets()) { + logger.trace("Saving port mapping {}", preset.getCompleteDescription()); + } + } + try { + getContext().getLocalStorage().save(settings, SETTINGS_FILENAME); + } catch (final IOException e) { + logger.warn("Could not save settings to file " + SETTINGS_FILENAME, e); + } + } + + public ResourceMap getResourceMap() { + return getContext().getResourceMap(); + } + + public PortMapperView getView() { + return (PortMapperView) getMainView(); + } + + public void connectRouter() throws RouterException { + if (this.router != null) { + logger.warn("Already connected to router. Cannot create a second connection."); + return; + } + + final AbstractRouterFactory routerFactory; + try { + routerFactory = createRouterFactory(); + } catch (final RouterException e) { + logger.error("Could not create router factory: " + e.getMessage(), e); + return; + } + logger.info("Searching for routers..."); + + final Collection foundRouters = routerFactory.findRouters(); + + // No routers found + if (foundRouters == null || foundRouters.size() == 0) { + throw new RouterException("Did not find a router"); + } + + // One router found: use it. + if (foundRouters.size() == 1) { + router = foundRouters.iterator().next(); + logger.info("Connected to router '{}'", router.getName()); + this.getView().fireConnectionStateChange(); + return; + } + + // More than one router found: ask user. + logger.info("Found more than one router (count: {}): ask user.", foundRouters.size()); + + final ResourceMap resourceMap = getResourceMap(); + final IRouter selectedRouter = (IRouter) JOptionPane.showInputDialog(this.getView().getFrame(), + resourceMap.getString("messages.select_router.message"), + resourceMap.getString("messages.select_router.title"), JOptionPane.QUESTION_MESSAGE, null, + foundRouters.toArray(), null); + + if (selectedRouter == null) { + logger.info("No router selected."); + return; + } + + this.router = selectedRouter; + this.getView().fireConnectionStateChange(); + } + + private AbstractRouterFactory createRouterFactory() throws RouterException { + logger.info("Creating router factory for class {}", settings.getRouterFactoryClassName()); + final Class routerFactoryClass = getClassForName(settings.getRouterFactoryClassName()); + final Constructor constructor = getConstructor(routerFactoryClass); + final AbstractRouterFactory routerFactory = createInstance(constructor); + logger.debug("Router factory {} created", routerFactory); + return routerFactory; + } + + private AbstractRouterFactory createInstance(final Constructor constructor) + throws RouterException { + try { + return constructor.newInstance(this); + } catch (final Exception e) { + throw new RouterException("Could not create a router factory using constructor " + constructor, e); + } + } + + private static Constructor getConstructor(final Class clazz) + throws RouterException { + try { + return clazz.getConstructor(PortMapperApp.class); + } catch (final NoSuchMethodException e) { + throw new RouterException("Could not find constructor of " + clazz.getName(), e); + } catch (final SecurityException e1) { + throw new RouterException("Could not find constructor of " + clazz.getName(), e1); + } + } + + @SuppressWarnings("unchecked") + private static Class getClassForName(final String className) throws RouterException { + try { + return (Class) Class.forName(className); + } catch (final ClassNotFoundException e) { + throw new RouterException("Did not find router factory class for name " + className, e); + } + } + + public boolean disconnectRouter() { + if (this.router == null) { + logger.warn("Not connected to router. Can not disconnect."); + return false; + } + + this.router.disconnect(); + this.router = null; + this.getView().fireConnectionStateChange(); + return true; + } + + public IRouter getRouter() { + return router; + } + + public Settings getSettings() { + return settings; + } + + public boolean isConnected() { + return this.getRouter() != null; + } + + /** + * Get the IP address of the local host. + * + * @return IP address of the local host or null, if the address could not be determined. + * @throws RouterException + */ + public String getLocalHostAddress() { + + try { + if (router != null) { + logger.debug("Connected to router, get IP of localhost from socket..."); + return router.getLocalHostAddress(); + } + + logger.debug("Not connected to router, get IP of localhost from network interface..."); + final InetAddress address = getLocalhostAddressFromNetworkInterface(); + if (address != null) { + return address.getHostAddress(); + } else { + logger.warn("Did not get IP of localhost from network interface"); + } + + } catch (final RouterException e) { + logger.warn("Could not get address of localhost.", e); + logger.warn("Could not get address of localhost. Please enter it manually."); + } + return null; + } + + private InetAddress getLocalhostAddressFromNetworkInterface() throws RouterException { + try { + final List networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + logger.trace("Found network interfaces " + networkInterfaces); + for (final NetworkInterface nInterface : networkInterfaces) { + if (nInterface.isLoopback()) { + logger.debug("Found loopback network interface " + nInterface.getDisplayName() + "/" + + nInterface.getName() + " with IPs " + nInterface.getInterfaceAddresses() + ": ignore."); + } else if (!nInterface.isUp()) { + logger.debug("Found inactive network interface " + nInterface.getDisplayName() + "/" + + nInterface.getName() + " with IPs " + nInterface.getInterfaceAddresses() + ": ignore."); + } else { + logger.debug("Found network interface " + nInterface.getDisplayName() + "/" + nInterface.getName() + + " with IPs " + nInterface.getInterfaceAddresses() + ": use this one."); + final List addresses = Collections.list(nInterface.getInetAddresses()); + if (addresses.size() > 0) { + final InetAddress address = findIPv4Adress(nInterface, addresses); + logger.debug("Found one address for network interface " + nInterface.getName() + ": using " + + address); + return address; + } + logger.debug("Network interface " + nInterface.getName() + " has no addresses."); + } + } + } catch (final SocketException e) { + throw new RouterException("Did not get network interfaces.", e); + } + return null; + } + + private InetAddress findIPv4Adress(final NetworkInterface nInterface, final List addresses) { + if (addresses.size() == 1) { + return addresses.get(0); + } + + for (final InetAddress inetAddress : addresses) { + if (inetAddress.getHostAddress().contains(".")) { + logger.debug("Found IPv4 address " + inetAddress); + return inetAddress; + } + } + final InetAddress address = addresses.get(0); + logger.info("Found more than one address for network interface " + nInterface.getName() + ": using " + address); + return address; + } + + public void setLogLevel(final String logLevel) { + this.logbackConfig.setLogLevel(logLevel); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/PortMapperCli.java b/UPnP/src/main/java/org/chris/portmapper/PortMapperCli.java new file mode 100644 index 0000000..7691cf2 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/PortMapperCli.java @@ -0,0 +1,238 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; +import org.chris.portmapper.router.cling.ClingRouterFactory; +import org.jdesktop.application.Application; +import org.jdesktop.application.utils.AppHelper; +import org.jdesktop.application.utils.PlatformType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PortMapperCli { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final CommandLineArguments cmdLineArgs; + private String routerFactoryClassName = ClingRouterFactory.class.getName(); + private Integer routerIndex = null; + + public PortMapperCli() { + cmdLineArgs = new CommandLineArguments(); + } + + public void start(final String[] args) { + if (!cmdLineArgs.parse(args)) { + System.exit(1); + } + if (isStartGuiRequired()) { + startGui(args); + return; + } + + if (cmdLineArgs.getUpnpLib() != null) { + this.routerFactoryClassName = cmdLineArgs.getUpnpLib(); + logger.info("Using router factory class '" + this.routerFactoryClassName + "'"); + } + + if (cmdLineArgs.getRouterIndex() != null) { + this.routerIndex = cmdLineArgs.getRouterIndex(); + logger.info("Using router index " + this.routerIndex); + } + + if (cmdLineArgs.isPrintHelp()) { + printHelp(); + return; + } + try { + final IRouter router = connect(); + if (router == null) { + logger.error("No router found: exit"); + System.exit(1); + return; + } + if (cmdLineArgs.isAddPortMapping()) { + addPortForwarding(router); + } else if (cmdLineArgs.isPrintInfo()) { + printStatus(router); + } else if (cmdLineArgs.isDeletePortMapping()) { + deletePortForwardings(router); + } else if (cmdLineArgs.isListPortMappings()) { + printPortForwardings(router); + } else { + router.disconnect(); + System.err.println("Incorrect usage"); + printHelp(); + System.exit(1); + return; + } + router.disconnect(); + } catch (final RouterException e) { + logger.error("An error occured", e); + System.exit(1); + return; + } + System.exit(0); + } + + private void startGui(final String[] args) { + if (AppHelper.getPlatform() == PlatformType.OS_X) { + MacSetup.setupMac(); + } + Application.launch(PortMapperApp.class, args); + } + + private void printPortForwardings(final IRouter router) throws RouterException { + final Collection mappings = router.getPortMappings(); + if (mappings.size() == 0) { + logger.info("No port mappings found"); + return; + } + final StringBuilder b = new StringBuilder(); + for (final Iterator iterator = mappings.iterator(); iterator.hasNext();) { + final PortMapping mapping = iterator.next(); + b.append(mapping.getCompleteDescription()); + if (iterator.hasNext()) { + b.append("\n"); + } + } + logger.info("Found " + mappings.size() + " port forwardings:\n" + b.toString()); + } + + private void deletePortForwardings(final IRouter router) throws RouterException { + + final String remoteHost = null; + final int port = cmdLineArgs.getExternalPort(); + final Protocol protocol = cmdLineArgs.getProtocol(); + logger.info("Deleting mapping for protocol " + protocol + " and external port " + port); + router.removePortMapping(protocol, remoteHost, port); + printPortForwardings(router); + } + + private void printStatus(final IRouter router) throws RouterException { + router.logRouterInfo(); + } + + private void addPortForwarding(final IRouter router) throws RouterException { + + final String remoteHost = null; + final String internalClient = cmdLineArgs.getInternalIp() != null ? cmdLineArgs.getInternalIp() + : router.getLocalHostAddress(); + final int internalPort = cmdLineArgs.getInternalPort(); + final int externalPort = cmdLineArgs.getExternalPort(); + final Protocol protocol = cmdLineArgs.getProtocol(); + + final String description = cmdLineArgs.getDescription() != null ? cmdLineArgs.getDescription() + : "PortMapper " + protocol + "/" + internalClient + ":" + internalPort; + final PortMapping mapping = new PortMapping(protocol, remoteHost, externalPort, internalClient, internalPort, + description); + logger.info("Adding mapping " + mapping); + router.addPortMapping(mapping); + printPortForwardings(router); + } + + private void printHelp() { + cmdLineArgs.printHelp(); + } + + private boolean isStartGuiRequired() { + if (cmdLineArgs.isStartGui()) { + return true; + } + return !(cmdLineArgs.isPrintHelp() || cmdLineArgs.isAddPortMapping() || cmdLineArgs.isPrintInfo() + || cmdLineArgs.isListPortMappings() || cmdLineArgs.isDeletePortMapping()); + } + + @SuppressWarnings("unchecked") + private AbstractRouterFactory createRouterFactory() throws RouterException { + Class routerFactoryClass; + logger.info("Creating router factory for class {}", routerFactoryClassName); + try { + routerFactoryClass = (Class) Class.forName(routerFactoryClassName); + } catch (final ClassNotFoundException e) { + throw new RouterException("Did not find router factory class for name " + routerFactoryClassName, e); + } + + logger.debug("Creating a new instance of the router factory class {}", routerFactoryClass); + try { + final Constructor constructor = routerFactoryClass + .getConstructor(PortMapperApp.class); + return constructor.newInstance(new PortMapperApp()); + } catch (final Exception e) { + throw new RouterException("Error creating a router factory using class " + routerFactoryClass.getName(), e); + } + } + + private IRouter connect() throws RouterException { + AbstractRouterFactory routerFactory; + try { + routerFactory = createRouterFactory(); + } catch (final RouterException e) { + logger.error("Could not create router factory", e); + return null; + } + logger.info("Searching for routers..."); + + final List foundRouters = routerFactory.findRouters(); + + return selectRouter(foundRouters); + } + + /** + * @param foundRouters + * @return + */ + private IRouter selectRouter(final List foundRouters) { + // One router found: use it. + if (foundRouters.size() == 1) { + final IRouter router = foundRouters.iterator().next(); + logger.info("Connected to router " + router.getName()); + return router; + } else if (foundRouters.size() == 0) { + logger.error("Found no router"); + return null; + } else if (foundRouters.size() > 1 && routerIndex == null) { + // let user choose which router to use. + logger.error("Found more than one router. Use option -i "); + + int index = 0; + for (final IRouter iRouter : foundRouters) { + logger.error("- index " + index + ": " + iRouter.getName()); + index++; + } + return null; + } else if (routerIndex >= 0 && routerIndex < foundRouters.size()) { + final IRouter router = foundRouters.get(routerIndex); + logger.info("Found more than one router, using " + router.getName()); + return router; + } else { + logger.error("Index must be between 0 and " + (foundRouters.size() - 1)); + return null; + } + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/PortMapperStarter.java b/UPnP/src/main/java/org/chris/portmapper/PortMapperStarter.java new file mode 100644 index 0000000..5ec8e7f --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/PortMapperStarter.java @@ -0,0 +1,43 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + +public class PortMapperStarter { + + private final static Logger LOG = LoggerFactory.getLogger(PortMapperStarter.class); + + public static void main(final String[] args) { + redirectJavaUtilLoggingToLogback(); + final PortMapperCli cli = new PortMapperCli(); + try { + cli.start(args); + } catch (final Exception e) { + LOG.error("PortMapper failed with exception " + e.getMessage(), e); + System.exit(1); + } + } + + private static void redirectJavaUtilLoggingToLogback() { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/Settings.java b/UPnP/src/main/java/org/chris/portmapper/Settings.java new file mode 100644 index 0000000..e92abc3 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/Settings.java @@ -0,0 +1,112 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.chris.portmapper.model.PortMappingPreset; +import org.chris.portmapper.router.cling.ClingRouterFactory; + +import ch.qos.logback.classic.Level; + +public class Settings implements Serializable { + + private static final long serialVersionUID = -1349121864190290050L; + + public final static String PROPERTY_PORT_MAPPING_PRESETS = "presets"; + + private List presets; + private boolean useEntityEncoding; + private String logLevel; + private String routerFactoryClassName; + + private transient PropertyChangeSupport propertyChangeSupport; + + public Settings() { + useEntityEncoding = true; + logLevel = Level.INFO.toString(); + presets = new ArrayList<>(); + routerFactoryClassName = ClingRouterFactory.class.getName(); + propertyChangeSupport = new PropertyChangeSupport(this); + } + + public void addPropertyChangeListener(final String property, final PropertyChangeListener listener) { + this.propertyChangeSupport.addPropertyChangeListener(property, listener); + } + + public List getPresets() { + return presets; + } + + public void setPresets(final List presets) { + this.presets = presets; + } + + public void addPreset(final PortMappingPreset newPreset) { + final List oldPresets = new ArrayList<>(this.presets); + this.presets.add(newPreset); + this.propertyChangeSupport.firePropertyChange(PROPERTY_PORT_MAPPING_PRESETS, oldPresets, + new ArrayList<>(this.presets)); + } + + public void removePresets(final PortMappingPreset selectedPreset) { + final List oldPresets = new ArrayList<>(this.presets); + this.presets.remove(selectedPreset); + this.propertyChangeSupport.firePropertyChange(PROPERTY_PORT_MAPPING_PRESETS, oldPresets, + new ArrayList<>(this.presets)); + } + + public void savePreset(final PortMappingPreset portMappingPreset) { + this.propertyChangeSupport.firePropertyChange(PROPERTY_PORT_MAPPING_PRESETS, null, + new ArrayList<>(this.presets)); + } + + @Override + public String toString() { + return "[Settings: presets=" + presets + ", useEntityEncoding=" + useEntityEncoding + ", logLevel=" + logLevel + + ", routerFactoryClassName=" + routerFactoryClassName + "]"; + } + + public boolean isUseEntityEncoding() { + return useEntityEncoding; + } + + public void setUseEntityEncoding(final boolean useEntityEncoding) { + this.useEntityEncoding = useEntityEncoding; + } + + public String getLogLevel() { + return this.logLevel; + } + + public void setLogLevel(final String logLevel) { + this.logLevel = logLevel; + } + + public String getRouterFactoryClassName() { + return routerFactoryClassName; + } + + public void setRouterFactoryClassName(final String routerFactoryClassName) { + this.routerFactoryClassName = routerFactoryClassName; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageListener.java b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageListener.java new file mode 100644 index 0000000..2c5adbc --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageListener.java @@ -0,0 +1,36 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.logging; + +import javax.swing.JTextArea; + +/** + * The {@link LogMessageWriter} copies every written string to a {@link LogMessageListener}. All written strings are + * buffered, so no string is missed. A {@link LogMessageListener} can be registered using method + * {@link #registerListener(JTextArea)}. + */ +public interface LogMessageListener { + + /** + * Process the given log message. This could mean e.g. to display the message to the user. + * + * @param message + * the message to process. + */ + public void addLogMessage(String message); +} diff --git a/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageOutputStream.java b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageOutputStream.java new file mode 100644 index 0000000..e383c90 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageOutputStream.java @@ -0,0 +1,104 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.logging; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JTextArea; + +/** + * This class implements an output stream that appends to a list and allows listeners to register for written lines. + */ +public class LogMessageOutputStream extends OutputStream { + + /** + * The listener to which the strings will be forwarded. + */ + private LogMessageListener logListener; + + /** + * The buffer to which the written strings are added until a listener is registered. + */ + private List unprocessedMessagesBuffer; + + /** + * Creates a new {@link LogMessageOutputStream}. At creation time, no listener is registered, so that all added text + * is stored in a buffer. + */ + public LogMessageOutputStream() { + unprocessedMessagesBuffer = new LinkedList<>(); + } + + @Override + public void close() { + // ignore + } + + @Override + public void flush() { + // ignore + } + + @Override + public void write(final byte b[], final int off, final int len) throws IOException { + final String line = new String(b, off, len); + addMessage(line); + } + + @Override + public void write(final int b) throws IOException { + throw new UnsupportedOperationException(); + } + + /** + * Append the given message to the registered {@link LogMessageListener}. If no listener is registered, the string + * is written to a buffer. When a listener is registered, the buffered text will be appended to the listener. + * + * @param message + * the message to append. + */ + public void addMessage(final String message) { + if (this.logListener != null) { + this.logListener.addLogMessage(message); + } else { + unprocessedMessagesBuffer.add(message); + } + } + + /** + * Registers a {@link JTextArea}, so that all strings written to this writer are appended to the given text area. + * After registration, all buffered strings are appended to the text area, so that no string is missed. + * + * @param textArea + * the text area to wich to append the strings. + */ + public void registerListener(final LogMessageListener textArea) { + this.logListener = textArea; + + // append the buffered text to the text area. + for (final String line : unprocessedMessagesBuffer) { + this.logListener.addLogMessage(line); + } + // we do not need the buffer any more, all text will be appended + // to the text area. + this.unprocessedMessagesBuffer = null; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageWriter.java b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageWriter.java new file mode 100644 index 0000000..97dec3c --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/logging/LogMessageWriter.java @@ -0,0 +1,100 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.logging; + +import java.io.Writer; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.JTextArea; + +/** + * The {@link LogMessageWriter} copies every written string to a {@link LogMessageListener}. All written strings are + * buffered, so no string is missed. A {@link LogMessageListener} can be registered using method + * {@link #registerListener(JTextArea)}. + */ +public class LogMessageWriter extends Writer { + + /** + * The listener to which the strings will be forwarded. + */ + private LogMessageListener logListener; + + /** + * The buffer to which the written strings are added until a listener is registered. + */ + private List unprocessedMessagesBuffer; + + /** + * Creates a new {@link LogMessageWriter}. At creation time, no listener is registered, so that all added text is + * stored in a buffer. + */ + public LogMessageWriter() { + unprocessedMessagesBuffer = new LinkedList<>(); + } + + @Override + public void close() { + // ignore + } + + @Override + public void flush() { + // ignore + } + + @Override + public void write(final char[] cbuf, final int off, final int len) { + final String line = new String(cbuf, off, len); + addMessage(line); + } + + /** + * Append the given message to the registered {@link LogMessageListener}. If no listener is registered, the string + * is written to a buffer. When a listener is registered, the buffered text will be appended to the listener. + * + * @param message + * the message to append. + */ + public void addMessage(final String message) { + if (this.logListener != null) { + this.logListener.addLogMessage(message); + } else { + unprocessedMessagesBuffer.add(message); + } + } + + /** + * Registers a {@link JTextArea}, so that all strings written to this writer are appended to the given text area. + * After registration, all buffered strings are appended to the text area, so that no string is missed. + * + * @param textArea + * the text area to wich to append the strings. + */ + public void registerListener(final LogMessageListener textArea) { + this.logListener = textArea; + + // append the buffered text to the text area. + for (final String line : unprocessedMessagesBuffer) { + this.logListener.addLogMessage(line); + } + // we do not need the buffer any more, all text will be appended + // to the text area. + this.unprocessedMessagesBuffer = null; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/logging/LogbackConfiguration.java b/UPnP/src/main/java/org/chris/portmapper/logging/LogbackConfiguration.java new file mode 100644 index 0000000..0867485 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/logging/LogbackConfiguration.java @@ -0,0 +1,83 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.logging; + +import java.io.OutputStream; + +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.OutputStreamAppender; +import ch.qos.logback.core.encoder.Encoder; + +public class LogbackConfiguration { + + private static final String PATTERN_LAYOUT = "%-5level %msg%n"; + private static final String OUTPUT_STREAM_APPENDER_NAME = "OUTPUT_STREAM"; + private static final String LOGGER_NAME = "ROOT"; + + private final LoggerContext loggerContext; + + public LogbackConfiguration() { + loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + } + + public void registerOutputStream(final OutputStream logMessageOutputStream) { + final Encoder encoder = createPatternLayoutEncoder(PATTERN_LAYOUT); + final OutputStreamAppender appender = createAppender(logMessageOutputStream, encoder); + configureLogger(appender); + } + + private void configureLogger(final OutputStreamAppender appender) { + final Logger logbackLogger = getLogger(); + logbackLogger.addAppender(appender); + logbackLogger.setAdditive(false); + } + + private Logger getLogger() { + return (Logger) LoggerFactory.getLogger(LOGGER_NAME); + } + + private OutputStreamAppender createAppender(final OutputStream logMessageOutputStream, + final Encoder encoder) { + final OutputStreamAppender appender = new OutputStreamAppender(); + appender.setContext(loggerContext); + appender.setEncoder(encoder); + appender.setOutputStream(logMessageOutputStream); + appender.setName(OUTPUT_STREAM_APPENDER_NAME); + appender.start(); + return appender; + } + + private PatternLayoutEncoder createPatternLayoutEncoder(final String pattern) { + final PatternLayoutEncoder encoder = new PatternLayoutEncoder(); + encoder.setContext(loggerContext); + encoder.setPattern(pattern); + encoder.start(); + return encoder; + } + + public void setLogLevel(final String logLevel) { + final Level level = Level.toLevel(logLevel); + getLogger().setLevel(level); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/model/PortMapping.java b/UPnP/src/main/java/org/chris/portmapper/model/PortMapping.java new file mode 100644 index 0000000..d970fd6 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/model/PortMapping.java @@ -0,0 +1,159 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.model; + +import java.util.HashMap; +import java.util.Map; + +import net.sbbi.upnp.messages.ActionResponse; + +/** + * This immutable class represents a port mapping / forwarding on a router. + */ +public class PortMapping implements Cloneable { + + public static final String MAPPING_ENTRY_LEASE_DURATION = "NewLeaseDuration"; + public static final String MAPPING_ENTRY_ENABLED = "NewEnabled"; + public static final String MAPPING_ENTRY_REMOTE_HOST = "NewRemoteHost"; + public static final String MAPPING_ENTRY_INTERNAL_CLIENT = "NewInternalClient"; + public static final String MAPPING_ENTRY_PORT_MAPPING_DESCRIPTION = "NewPortMappingDescription"; + public static final String MAPPING_ENTRY_PROTOCOL = "NewProtocol"; + public static final String MAPPING_ENTRY_INTERNAL_PORT = "NewInternalPort"; + public static final String MAPPING_ENTRY_EXTERNAL_PORT = "NewExternalPort"; + + private static final long DEFAULT_LEASE_DURATION = 0; + + private final int externalPort; + private final Protocol protocol; + private final int internalPort; + private final String description; + private final String internalClient; + private final String remoteHost; + private final boolean enabled; + private final long leaseDuration; + + public PortMapping(final Protocol protocol, final String remoteHost, final int externalPort, + final String internalClient, final int internalPort, final String description) { + this(protocol, remoteHost, externalPort, internalClient, internalPort, description, true, + DEFAULT_LEASE_DURATION); + } + + public PortMapping(final Protocol protocol, final String remoteHost, final int externalPort, + final String internalClient, final int internalPort, final String description, final boolean enabled, + final long leaseDuration) { + this.protocol = protocol; + this.remoteHost = remoteHost; + this.externalPort = externalPort; + this.internalClient = internalClient; + this.internalPort = internalPort; + this.description = description; + this.enabled = enabled; + this.leaseDuration = leaseDuration; + } + + private PortMapping(final ActionResponse response) { + final Map values = new HashMap<>(); + + for (final Object argObj : response.getOutActionArgumentNames()) { + final String argName = (String) argObj; + values.put(argName, response.getOutActionArgumentValue(argName)); + } + + externalPort = Integer.parseInt(values.get(MAPPING_ENTRY_EXTERNAL_PORT)); + internalPort = Integer.parseInt(values.get(MAPPING_ENTRY_INTERNAL_PORT)); + final String protocolString = values.get(MAPPING_ENTRY_PROTOCOL); + protocol = (protocolString.equalsIgnoreCase("TCP") ? Protocol.TCP : Protocol.UDP); + description = values.get(MAPPING_ENTRY_PORT_MAPPING_DESCRIPTION); + internalClient = values.get(MAPPING_ENTRY_INTERNAL_CLIENT); + remoteHost = values.get(MAPPING_ENTRY_REMOTE_HOST); + final String enabledString = values.get(MAPPING_ENTRY_ENABLED); + enabled = enabledString != null && enabledString.equals("1"); + leaseDuration = Long.parseLong(values.get(MAPPING_ENTRY_LEASE_DURATION)); + } + + public static PortMapping create(final ActionResponse response) { + final PortMapping mapping = new PortMapping(response); + return mapping; + } + + /** + * @return the leaseDuration + */ + public long getLeaseDuration() { + return leaseDuration; + } + + public int getExternalPort() { + return externalPort; + } + + public Protocol getProtocol() { + return protocol; + } + + public int getInternalPort() { + return internalPort; + } + + public String getDescription() { + return description; + } + + public String getInternalClient() { + return internalClient; + } + + public String getRemoteHost() { + return remoteHost; + } + + public boolean isEnabled() { + return enabled; + } + + public String getCompleteDescription() { + final StringBuilder b = new StringBuilder(); + b.append(protocol); + b.append(" "); + if (remoteHost != null) { + b.append(remoteHost); + } + b.append(":"); + b.append(externalPort); + b.append(" -> "); + b.append(internalClient); + b.append(":"); + b.append(internalPort); + b.append(" "); + b.append(enabled ? "enabled" : "not enabled"); + b.append(" "); + b.append(description); + return b.toString(); + } + + @Override + public String toString() { + return description; + } + + @Override + public Object clone() { + return new PortMapping(protocol, remoteHost, externalPort, internalClient, internalPort, description, enabled, + leaseDuration); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/model/PortMappingPreset.java b/UPnP/src/main/java/org/chris/portmapper/model/PortMappingPreset.java new file mode 100644 index 0000000..97737cc --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/model/PortMappingPreset.java @@ -0,0 +1,173 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.chris.portmapper.Settings; + +/** + * This class stores a port mapping preset containing a description, internal and remote host and a {@link List} of + * {@link SinglePortMapping}s. + */ +public class PortMappingPreset implements Cloneable, Serializable { + + private static final long serialVersionUID = 3749136884938395765L; + + /** + * The description of this preset. + */ + private String description; + + /** + * The ip address of the internal client or null for localhost. + */ + private String internalClient; + + /** + * The host name of the remote host. + */ + private String remoteHost; + + /** + * The port mappings in this preset. + */ + private List ports; + + /** + * true if this preset has not been saved. + */ + private boolean isNew; + + /** + * Creates a new preset with the given default values. + */ + public PortMappingPreset(final String remoteHost, final String internalClient, final String description) { + this.remoteHost = remoteHost; + this.internalClient = internalClient; + this.description = description; + this.ports = new LinkedList<>(); + + this.isNew = false; + } + + /** + * Creates a new empty preset. + */ + public PortMappingPreset() { + this.ports = new LinkedList<>(); + this.isNew = true; + } + + @Override + public String toString() { + return description; + } + + public List getPortMappings(final String localhost) { + if (this.useLocalhostAsInternalClient() && (localhost == null || localhost.length() == 0)) { + throw new IllegalArgumentException("Got invalid localhost and internal host is not given."); + } + + final List allPortMappings = new ArrayList<>(this.ports.size()); + for (final SinglePortMapping port : this.ports) { + final String internalClientName = this.useLocalhostAsInternalClient() ? localhost : this.internalClient; + + final PortMapping newMapping = new PortMapping(port.getProtocol(), remoteHost, port.getExternalPort(), + internalClientName, port.getInternalPort(), description); + + allPortMappings.add(newMapping); + } + + return allPortMappings; + } + + public String getCompleteDescription() { + final StringBuffer b = new StringBuffer(); + + b.append(" "); + b.append(remoteHost); + b.append(":"); + + b.append(" -> "); + b.append(internalClient); + b.append(":"); + + b.append(" "); + + b.append(" "); + b.append(description); + return b.toString(); + } + + public List getPorts() { + return ports; + } + + public void setPorts(final List ports) { + this.ports = ports; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setRemoteHost(final String remoteHost) { + this.remoteHost = remoteHost; + } + + public String getRemoteHost() { + return remoteHost; + } + + public void setInternalClient(final String internalClient) { + this.internalClient = internalClient; + } + + public String getInternalClient() { + return internalClient; + } + + public boolean isNew() { + return isNew; + } + + public void setNew(final boolean isNew) { + this.isNew = isNew; + } + + public boolean useLocalhostAsInternalClient() { + return this.getInternalClient() == null || this.getInternalClient().length() == 0; + } + + public void save(final Settings settings) { + if (this.isNew) { + settings.addPreset(this); + } else { + settings.savePreset(this); + } + this.isNew = false; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/model/Protocol.java b/UPnP/src/main/java/org/chris/portmapper/model/Protocol.java new file mode 100644 index 0000000..028b9d2 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/model/Protocol.java @@ -0,0 +1,50 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.model; + +/** + * This {@link Enum} represents the protocol of a {@link SinglePortMapping}, possible values are {@link #TCP} and + * {@link #UDP}. + */ +public enum Protocol { + + TCP("TCP"), UDP("UDP"); + + private final String name; + + private Protocol(final String name) { + this.name = name; + } + + public static Protocol getProtocol(final String name) { + if (name != null && name.equalsIgnoreCase("TCP")) { + return TCP; + } + if (name != null && name.equalsIgnoreCase("UDP")) { + return UDP; + } + throw new IllegalArgumentException("Invalid protocol name '" + name + "'"); + } + + public String getName() { + return name; + } +} \ No newline at end of file diff --git a/UPnP/src/main/java/org/chris/portmapper/model/SinglePortMapping.java b/UPnP/src/main/java/org/chris/portmapper/model/SinglePortMapping.java new file mode 100644 index 0000000..e47e5e9 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/model/SinglePortMapping.java @@ -0,0 +1,74 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.model; + +import java.io.Serializable; + +/** + * This class is used by {@link PortMapping} to store the information about a single port mapping, i.e. the protocol + * (TCP or UDP) and internal and extern port. + */ +public class SinglePortMapping implements Cloneable, Serializable { + + private static final long serialVersionUID = 7458514232916039775L; + private int externalPort; + private int internalPort; + private Protocol protocol; + + public SinglePortMapping() { + this(Protocol.TCP, 1, 1); + } + + public SinglePortMapping(final Protocol protocol, final int internalPort, final int externalPort) { + this.protocol = protocol; + this.internalPort = internalPort; + this.externalPort = externalPort; + } + + public int getExternalPort() { + return externalPort; + } + + public void setExternalPort(final int externalPort) { + this.externalPort = externalPort; + } + + public Protocol getProtocol() { + return protocol; + } + + public void setProtocol(final Protocol protocol) { + this.protocol = protocol; + } + + public int getInternalPort() { + return internalPort; + } + + public void setInternalPort(final int internalPort) { + this.internalPort = internalPort; + } + + @Override + public Object clone() { + return new SinglePortMapping(protocol, internalPort, externalPort); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouter.java new file mode 100644 index 0000000..440b6b1 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouter.java @@ -0,0 +1,120 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the abstract super class for all routers. + */ +public abstract class AbstractRouter implements IRouter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final String name; + + public AbstractRouter(final String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + /** + * Get the the ip of the local host. + */ + @Override + public String getLocalHostAddress() throws RouterException { + logger.debug("Get IP of localhost"); + + final InetAddress localHostIP = getLocalHostAddressFromSocket(); + + // We do not want an address like 127.0.0.1 + if (localHostIP.getHostAddress().startsWith("127.")) { + throw new RouterException("Only found an address that begins with '127.' when retrieving IP of localhost"); + } + + return localHostIP.getHostAddress(); + } + + /** + * Get the ip of the local host by connecting to the router and fetching the ip from the socket. This only works + * when we are connected to the router and know its internal upnp port. + * + * @return the ip of the local host. + * @throws RouterException + */ + private InetAddress getLocalHostAddressFromSocket() throws RouterException { + InetAddress localHostIP = null; + try { + + // In order to use the socket method to get the address, we have to + // be connected to the router. + final int routerInternalPort = getInternalPort(); + logger.debug("Got internal router port {}", routerInternalPort); + + // Check, if we got a correct port number + if (routerInternalPort > 0) { + logger.debug("Creating socket to router: {}:{}...", getInternalHostName(), routerInternalPort); + try (Socket socket = new Socket(getInternalHostName(), routerInternalPort)) { + localHostIP = socket.getLocalAddress(); + } catch (final UnknownHostException e) { + throw new RouterException( + "Could not create socked to " + getInternalHostName() + ":" + routerInternalPort, e); + } + + logger.debug("Got address {} from socket.", localHostIP); + } else { + logger.debug("Got invalid internal router port number {}", routerInternalPort); + } + + // We are not connected to the router or got an invalid port number, + // so we have to use the traditional method. + if (localHostIP == null) { + + logger.debug( + "Not connected to router or got invalid port number, can not use socket to determine the address of the localhost. " + + "If no address is found, please connect to the router."); + + localHostIP = InetAddress.getLocalHost(); + + logger.debug("Got address {} via InetAddress.getLocalHost().", localHostIP); + } + + } catch (final IOException e) { + throw new RouterException("Could not get IP of localhost.", e); + } + return localHostIP; + } + + @Override + public String toString() { + return getName() + " (" + getInternalHostName() + ")"; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouterFactory.java b/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouterFactory.java new file mode 100644 index 0000000..7459df9 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/AbstractRouterFactory.java @@ -0,0 +1,91 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router; + +import static java.util.Arrays.*; + +import java.util.Collection; +import java.util.List; + +import org.chris.portmapper.PortMapperApp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The base class for all router factories. + */ +public abstract class AbstractRouterFactory { + + private static final String LOCATION_URL_SYSTEM_PROPERTY = "portmapper.locationUrl"; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final PortMapperApp app; + + private final String name; + + protected AbstractRouterFactory(final PortMapperApp app, final String name) { + this.app = app; + this.name = name; + } + + /** + * Get the name of the router factory that can be displayed to the user. + * + * @return the name of the router factory that can be displayed to the user. + */ + public String getName() { + return name; + } + + public List findRouters() throws RouterException { + final String locationUrl = System.getProperty(LOCATION_URL_SYSTEM_PROPERTY); + if (locationUrl == null) { + logger.debug("System property '{}' not defined: discover routers automatically.", + LOCATION_URL_SYSTEM_PROPERTY); + return findRoutersInternal(); + } + logger.info("Trying to connect using location url {}", locationUrl); + return asList(connect(locationUrl)); + } + + /** + * Search for routers on the network. + * + * @return the found router or an empty {@link Collection} if no router was found. + * @throws RouterException + * if something goes wrong during discovery. + */ + protected abstract List findRoutersInternal() throws RouterException; + + /** + * Directly connect to a router using a location url like http://192.168.179.1:49000/igddesc.xml. + * + * @param locationUrl + * a location url + * @return a router if the connection was successful. + * @throws RouterException + * if something goes wrong during connection. + */ + protected abstract IRouter connect(final String locationUrl) throws RouterException; + + @Override + public String toString() { + return name; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/IRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/IRouter.java new file mode 100644 index 0000000..e19242b --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/IRouter.java @@ -0,0 +1,121 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router; + +import java.util.Collection; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; + +public interface IRouter { + + public abstract String getName(); + + /** + * Get the IP address of the local host. + * + * @return IP address of the local host or null, if the address could not be determined. + * @throws RouterException + */ + public String getLocalHostAddress() throws RouterException; + + /** + * Get the external IP of the router. + * + * @return the external IP of the router. + */ + public abstract String getExternalIPAddress() throws RouterException; + + /** + * Get the internal host name or IP of the router. + * + * @return the internal host name or IP of the router. + * @throws RouterException + */ + public abstract String getInternalHostName(); + + /** + * Get the internal port of the router. + * + * @return the internal port of the router. + * @throws RouterException + */ + public abstract int getInternalPort() throws RouterException; + + /** + * Get all port mappings from the router. + * + * @return all port mappings from the router. + * @throws RouterException + * if something went wrong when getting the port mappings. + */ + public abstract Collection getPortMappings() throws RouterException; + + /** + * Write information about the router to the log. + * + * @throws RouterException + */ + public abstract void logRouterInfo() throws RouterException; + + /** + * Add the given port mappings to the router. + * + * @param mappings + * the port mappings to add. + * @throws RouterException + */ + public abstract void addPortMappings(Collection mappings) throws RouterException; + + /** + * Add the given port mapping to the router. + * + * @param mapping + * the port mapping to add. + * @throws RouterException + */ + public abstract void addPortMapping(PortMapping mapping) throws RouterException; + + /** + * Remove the given port mapping from the router. + * + * @param mapping + * the port mapping to remove. + * @throws RouterException + */ + public abstract void removeMapping(PortMapping mapping) throws RouterException; + + /** + * Remove the port mapping with the given data from the router. + * + * @param protocol + * @param remoteHost + * @param externalPort + * @throws RouterException + */ + public abstract void removePortMapping(Protocol protocol, String remoteHost, int externalPort) + throws RouterException; + + /** + * Disconnect from the router. + */ + public abstract void disconnect(); +} \ No newline at end of file diff --git a/UPnP/src/main/java/org/chris/portmapper/router/RouterException.java b/UPnP/src/main/java/org/chris/portmapper/router/RouterException.java new file mode 100644 index 0000000..6f27efd --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/RouterException.java @@ -0,0 +1,31 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router; + +public class RouterException extends Exception { + + private static final long serialVersionUID = 1L; + + public RouterException(final String message, final Throwable cause) { + super(message, cause); + } + + public RouterException(final String message) { + super(message); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingOperationFailedException.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingOperationFailedException.java new file mode 100644 index 0000000..c44259c --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingOperationFailedException.java @@ -0,0 +1,36 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling; + +import org.fourthline.cling.model.message.control.IncomingActionResponseMessage; + +public class ClingOperationFailedException extends ClingRouterException { + + private static final long serialVersionUID = 1L; + private final IncomingActionResponseMessage response; + + public ClingOperationFailedException(final String message, final IncomingActionResponseMessage response) { + super(message); + assert response.getOperation().isFailed(); + this.response = response; + } + + public IncomingActionResponseMessage getResponse() { + return response; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingPortMappingExtractor.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingPortMappingExtractor.java new file mode 100644 index 0000000..63e9e88 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingPortMappingExtractor.java @@ -0,0 +1,154 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling; + +import java.util.Collection; +import java.util.LinkedList; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.router.RouterException; +import org.chris.portmapper.router.cling.action.ActionService; +import org.chris.portmapper.router.cling.action.GetPortMappingEntryAction; +import org.fourthline.cling.model.message.control.IncomingActionResponseMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sbbi.upnp.impls.InternetGatewayDevice; + +/** + * This class fetches all {@link PortMapping} from an {@link InternetGatewayDevice}. + */ +class ClingPortMappingExtractor { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Collection mappings; + private boolean moreEntries; + private int currentMappingNumber; + + /** + * The maximum number of port mappings that we will try to retrieve from the router. + */ + private final int maxNumPortMappings; + private final ActionService actionService; + + ClingPortMappingExtractor(final ActionService actionService, final int maxNumPortMappings) { + this.actionService = actionService; + this.maxNumPortMappings = maxNumPortMappings; + this.mappings = new LinkedList<>(); + this.moreEntries = true; + this.currentMappingNumber = 0; + } + + public Collection getPortMappings() throws RouterException { + + /* + * This is a little trick to get all port mappings. There is a method that gets the number of available port + * mappings (getNatMappingsCount()), but it seems, that this method just tries to get all port mappings and + * checks, if an error is returned. + * + * In order to speed this up, we will do the same here, but stop, when the first exception is thrown. + */ + + while (morePortMappingsAvailable()) { + logger.debug("Getting port mapping with entry number " + currentMappingNumber + "..."); + + try { + final PortMapping portMapping = actionService + .run(new GetPortMappingEntryAction(actionService.getService(), currentMappingNumber)); + mappings.add(portMapping); + } catch (final ClingOperationFailedException e) { + handleFailureResponse(e.getResponse()); + } + currentMappingNumber++; + } + + checkMaxNumPortMappingsReached(); + + return mappings; + } + + /** + * Check, if the max number of entries is reached and print a warning message. + */ + private void checkMaxNumPortMappingsReached() { + if (currentMappingNumber == maxNumPortMappings) { + logger.warn( + "Reached max number of port mappings to get ({}). Perhaps not all port mappings where retrieved.", + maxNumPortMappings); + } + } + + private boolean morePortMappingsAvailable() { + return moreEntries && currentMappingNumber < maxNumPortMappings; + } + + private void handleFailureResponse(final IncomingActionResponseMessage incomingActionResponseMessage) { + if (isNoMoreMappingsException(incomingActionResponseMessage)) { + moreEntries = false; + logger.debug("Got no port mapping for entry number {} (status: {}). Stop getting more entries.", + currentMappingNumber, incomingActionResponseMessage.getOperation().getStatusMessage()); + } else { + moreEntries = false; + logger.info( + "Got error response when fetching port mapping for entry number {}: '{}'. Stop getting more entries.", + currentMappingNumber, incomingActionResponseMessage); + } + } + + /** + * This method checks, if the error code of the given exception means, that no more mappings are available. + *

+ * The following error codes are recognized: + *

    + *
  • SpecifiedArrayIndexInvalid: 713
  • + *
  • NoSuchEntryInArray: 714
  • + *
  • Invalid Args: 402 (e.g. for DD-WRT, TP-LINK TL-R460 firmware 4.7.6 Build 100714 Rel.63134n)
  • + *
  • Other errors, e.g. "The reference to entity "T" must end with the ';' delimiter" or + * "Content is not allowed in prolog": 899 (e.g. ActionTec MI424-WR, Thomson TWG850-4U)
  • + *
+ * See bug reports + *
+ * + * @param incomingActionResponseMessage + * the exception to check + * @return true, if the given exception means, that no more port mappings are available, else + * false. + */ + private boolean isNoMoreMappingsException(final IncomingActionResponseMessage incomingActionResponseMessage) { + final int errorCode = incomingActionResponseMessage.getOperation().getStatusCode(); + switch (errorCode) { + case 713: + case 714: + case 402: + case 899: + return true; + + default: + return false; + } + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRegistryListener.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRegistryListener.java new file mode 100644 index 0000000..d1f0f4e --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRegistryListener.java @@ -0,0 +1,110 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +import org.fourthline.cling.model.meta.Device; +import org.fourthline.cling.model.meta.Service; +import org.fourthline.cling.model.types.DeviceType; +import org.fourthline.cling.model.types.ServiceType; +import org.fourthline.cling.model.types.UDADeviceType; +import org.fourthline.cling.model.types.UDAServiceType; +import org.fourthline.cling.registry.DefaultRegistryListener; +import org.fourthline.cling.registry.Registry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClingRegistryListener extends DefaultRegistryListener { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + public static final DeviceType IGD_DEVICE_TYPE = new UDADeviceType("InternetGatewayDevice", 1); + public static final DeviceType CONNECTION_DEVICE_TYPE = new UDADeviceType("WANConnectionDevice", 1); + + public static final ServiceType IP_SERVICE_TYPE = new UDAServiceType("WANIPConnection", 1); + public static final ServiceType PPP_SERVICE_TYPE = new UDAServiceType("WANPPPConnection", 1); + + private final SynchronousQueue> foundServices; + + public ClingRegistryListener() { + this.foundServices = new SynchronousQueue<>(); + } + + public Service waitForServiceFound(final long timeout, final TimeUnit unit) { + try { + return foundServices.poll(timeout, unit); + } catch (final InterruptedException e) { + logger.warn("Interrupted when waiting for a service"); + return null; + } + } + + @Override + public void deviceAdded(final Registry registry, @SuppressWarnings("rawtypes") final Device device) { + @SuppressWarnings("unchecked") + final Service connectionService = discoverConnectionService(device); + if (connectionService == null) { + return; + } + + logger.debug("Found connection service {}", connectionService); + foundServices.offer(connectionService); + } + + protected Service discoverConnectionService(@SuppressWarnings("rawtypes") final Device device) { + if (!device.getType().equals(IGD_DEVICE_TYPE)) { + logger.debug("Found service of wrong type {}, expected {}.", device.getType(), IGD_DEVICE_TYPE); + return null; + } + + @SuppressWarnings("rawtypes") + final Device[] connectionDevices = device.findDevices(CONNECTION_DEVICE_TYPE); + if (connectionDevices.length == 0) { + logger.debug("IGD doesn't support '{}': {}", CONNECTION_DEVICE_TYPE, device); + return null; + } + + logger.debug("Found {} devices", connectionDevices.length); + + return findConnectionService(connectionDevices); + } + + @SuppressWarnings("rawtypes") + private Service findConnectionService(final Device[] connectionDevices) { + for (final Device connectionDevice : connectionDevices) { + + final Service ipConnectionService = connectionDevice.findService(IP_SERVICE_TYPE); + final Service pppConnectionService = connectionDevice.findService(PPP_SERVICE_TYPE); + + if (ipConnectionService != null) { + logger.debug("Device {} supports ip service type: {}", connectionDevice, ipConnectionService); + return ipConnectionService; + } + if (pppConnectionService != null) { + logger.debug("Device {} supports ppp service type: {}", connectionDevice, pppConnectionService); + return pppConnectionService; + } + + logger.debug("IGD {} doesn't support IP or PPP WAN connection service", connectionDevice); + } + logger.debug("None of the {} devices supports IP or PPP WAN connections", connectionDevices.length); + return null; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouter.java new file mode 100644 index 0000000..3f66ba9 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouter.java @@ -0,0 +1,145 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling; + +import java.net.URI; +import java.util.Collection; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.chris.portmapper.router.AbstractRouter; +import org.chris.portmapper.router.RouterException; +import org.chris.portmapper.router.cling.action.ActionService; +import org.chris.portmapper.router.cling.action.AddPortMappingAction; +import org.chris.portmapper.router.cling.action.DeletePortMappingAction; +import org.chris.portmapper.router.cling.action.GetExternalIpAction; +import org.fourthline.cling.controlpoint.ControlPoint; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.meta.Service; +import org.fourthline.cling.model.meta.UDAVersion; +import org.fourthline.cling.registry.Registry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClingRouter extends AbstractRouter { + + /** + * The maximum number of port mappings that we will try to retrieve from the router. + */ + private final static int MAX_NUM_PORTMAPPINGS = 500; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final RemoteService service; + + private final Registry registry; + + private final ActionService actionService; + + public ClingRouter(final RemoteService service, final Registry registry, final ControlPoint controlPoint) { + super(getName(service)); + this.service = service; + this.registry = registry; + actionService = new ActionService(service, controlPoint); + } + + private static String getName(final Service service) { + return service.getDevice().getDisplayString(); + } + + @Override + public String getExternalIPAddress() throws RouterException { + return actionService.run(new GetExternalIpAction(service)); + } + + @Override + public String getInternalHostName() { + final URI uri = getUri(); + return uri != null ? uri.getHost() : null; + } + + @Override + public int getInternalPort() throws RouterException { + final URI uri = getUri(); + return uri != null ? uri.getPort() : null; + } + + private URI getUri() { + if (service.getDevice().getDetails().getPresentationURI() != null) { + return service.getDevice().getDetails().getPresentationURI(); + } + if (service.getControlURI() != null) { + return service.getControlURI(); + } + if (service.getDescriptorURI() != null) { + return service.getDescriptorURI(); + } + if (service.getEventSubscriptionURI() != null) { + return service.getEventSubscriptionURI(); + } + return null; + } + + @Override + public Collection getPortMappings() throws RouterException { + return new ClingPortMappingExtractor(actionService, MAX_NUM_PORTMAPPINGS).getPortMappings(); + } + + @Override + public void logRouterInfo() throws RouterException { + logger.info("Service id: " + service.getServiceId()); + logger.info("Reference: " + service.getReference()); + logger.info("Display name: " + service.getDevice().getDisplayString()); + final UDAVersion version = service.getDevice().getVersion(); + logger.info("Version: " + version.getMajor() + "." + version.getMinor()); + logger.info("Control uri: {}", service.getControlURI()); + logger.info("Descriptor uri: {}", service.getDescriptorURI()); + logger.info("Event subscription uri: {}", service.getEventSubscriptionURI()); + logger.info("Device base url: {}", service.getDevice().getDetails().getBaseURL()); + logger.info("Device presentation uri: {}", service.getDevice().getDetails().getPresentationURI()); + } + + @Override + public void addPortMappings(final Collection mappings) throws RouterException { + for (final PortMapping portMapping : mappings) { + addPortMapping(portMapping); + } + } + + @Override + public void addPortMapping(final PortMapping mapping) throws RouterException { + actionService.run(new AddPortMappingAction(service, mapping)); + } + + @Override + public void removeMapping(final PortMapping mapping) throws RouterException { + actionService.run(new DeletePortMappingAction(service, mapping)); + } + + @Override + public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort) + throws RouterException { + removeMapping(new PortMapping(protocol, remoteHost, externalPort, null, 0, null)); + } + + @Override + public void disconnect() { + logger.debug("Shutdown registry"); + registry.shutdown(); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterException.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterException.java new file mode 100644 index 0000000..45039fc --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterException.java @@ -0,0 +1,34 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.cling; + +public class ClingRouterException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ClingRouterException(final String message, final Throwable cause) { + super(message, cause); + } + + public ClingRouterException(final String message) { + super(message); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterFactory.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterFactory.java new file mode 100644 index 0000000..d0662d3 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/ClingRouterFactory.java @@ -0,0 +1,89 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EventObject; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.chris.portmapper.PortMapperApp; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; +import org.fourthline.cling.DefaultUpnpServiceConfiguration; +import org.fourthline.cling.UpnpService; +import org.fourthline.cling.UpnpServiceConfiguration; +import org.fourthline.cling.UpnpServiceImpl; +import org.fourthline.cling.model.meta.RemoteService; +import org.jdesktop.application.Application.ExitListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClingRouterFactory extends AbstractRouterFactory { + + private static final long DISCOVERY_TIMEOUT_SECONDS = 5; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + public ClingRouterFactory(final PortMapperApp app) { + super(app, "Cling lib"); + } + + @Override + protected List findRoutersInternal() throws RouterException { + final UpnpServiceConfiguration config = new DefaultUpnpServiceConfiguration(); + final ClingRegistryListener clingRegistryListener = new ClingRegistryListener(); + final UpnpService upnpService = new UpnpServiceImpl(config, clingRegistryListener); + shutdownServiceOnExit(upnpService); + + log.debug("Start searching using upnp service"); + upnpService.getControlPoint().search(); + final RemoteService service = (RemoteService) clingRegistryListener + .waitForServiceFound(DISCOVERY_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + if (service == null) { + log.debug("Did not find a service after {} seconds", DISCOVERY_TIMEOUT_SECONDS); + return Collections.emptyList(); + } + + log.debug("Found service {}", service); + return Arrays + . asList(new ClingRouter(service, upnpService.getRegistry(), upnpService.getControlPoint())); + } + + private void shutdownServiceOnExit(final UpnpService upnpService) { + app.addExitListener(new ExitListener() { + @Override + public void willExit(final EventObject event) { + log.debug("Shutdown upnp service"); + upnpService.shutdown(); + } + + @Override + public boolean canExit(final EventObject event) { + return true; + } + }); + } + + @Override + protected IRouter connect(final String locationUrl) throws RouterException { + throw new UnsupportedOperationException("Direct connection is not supported for Cling library."); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AbstractClingAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AbstractClingAction.java new file mode 100644 index 0000000..ac80c13 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AbstractClingAction.java @@ -0,0 +1,83 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.chris.portmapper.router.cling.ClingRouterException; +import org.fourthline.cling.model.action.ActionArgumentValue; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.Action; +import org.fourthline.cling.model.meta.ActionArgument; +import org.fourthline.cling.model.meta.ActionArgument.Direction; +import org.fourthline.cling.model.meta.RemoteDevice; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.meta.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractClingAction implements ClingAction { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Service service; + private final String actionName; + + public AbstractClingAction(final Service service, final String actionName) { + this.service = service; + this.actionName = actionName; + } + + public Map getArgumentValues() { + return Collections.emptyMap(); + } + + @SuppressWarnings("unchecked") + @Override + public ActionInvocation getActionInvocation() { + final Action action = service.getAction(actionName); + if (action == null) { + throw new ClingRouterException("No action found for name '" + actionName + "'. Available actions: " + + Arrays.toString(service.getActions())); + } + @SuppressWarnings("rawtypes") + final ActionArgumentValue[] argumentArray = getArguments(action); + return new ActionInvocation(action, argumentArray); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private ActionArgumentValue[] getArguments(final Action action) { + final ActionArgument[] actionArguments = action.getArguments(); + final Map argumentValues = getArgumentValues(); + final List> actionArgumentValues = new ArrayList<>(actionArguments.length); + + for (final ActionArgument actionArgument : actionArguments) { + if (actionArgument.getDirection() == Direction.IN) { + final Object value = argumentValues.get(actionArgument.getName()); + logger.trace("Action {}: add arg value for {}: {} (expected datatype: {})", action.getName(), + actionArgument, value, actionArgument.getDatatype().getDisplayString()); + actionArgumentValues.add(new ActionArgumentValue<>(actionArgument, value)); + } + } + return actionArgumentValues.toArray(new ActionArgumentValue[actionArgumentValues.size()]); + } +} \ No newline at end of file diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ActionService.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ActionService.java new file mode 100644 index 0000000..3a77bbb --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ActionService.java @@ -0,0 +1,60 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import java.net.URL; + +import org.chris.portmapper.router.cling.ClingOperationFailedException; +import org.chris.portmapper.router.cling.ClingRouterException; +import org.fourthline.cling.controlpoint.ControlPoint; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.message.control.IncomingActionResponseMessage; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.protocol.sync.SendingAction; + +public class ActionService { + private final RemoteService remoteService; + private final ControlPoint controlPoint; + + public ActionService(final RemoteService remoteService, final ControlPoint controlPoint) { + this.remoteService = remoteService; + this.controlPoint = controlPoint; + } + + public T run(final ClingAction action) { + // Figure out the remote URL where we'd like to send the action request to + final URL controLURL = remoteService.getDevice().normalizeURI(remoteService.getControlURI()); + + final ActionInvocation actionInvocation = action.getActionInvocation(); + final SendingAction prot = controlPoint.getProtocolFactory().createSendingAction(actionInvocation, controLURL); + prot.run(); + + final IncomingActionResponseMessage response = prot.getOutputMessage(); + if (response == null) { + throw new ClingRouterException("Got null response for action " + actionInvocation); + } else if (response.getOperation().isFailed()) { + throw new ClingOperationFailedException("Invocation " + actionInvocation + " failed with operation '" + + response.getOperation() + "', body '" + response.getBodyString() + "'", response); + } + return action.convert(actionInvocation); + } + + public RemoteService getService() { + return remoteService; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AddPortMappingAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AddPortMappingAction.java new file mode 100644 index 0000000..cb8fa84 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/AddPortMappingAction.java @@ -0,0 +1,58 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import java.util.HashMap; +import java.util.Map; + +import org.chris.portmapper.model.PortMapping; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.RemoteDevice; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.meta.Service; +import org.fourthline.cling.model.types.UnsignedIntegerFourBytes; +import org.fourthline.cling.model.types.UnsignedIntegerTwoBytes; + +public class AddPortMappingAction extends AbstractClingAction { + + private final PortMapping portMapping; + + public AddPortMappingAction(final Service service, final PortMapping portMapping) { + super(service, "AddPortMapping"); + this.portMapping = portMapping; + } + + @Override + public Map getArgumentValues() { + final HashMap args = new HashMap<>(); + args.put("NewExternalPort", new UnsignedIntegerTwoBytes(portMapping.getExternalPort())); + args.put("NewProtocol", portMapping.getProtocol()); + args.put("NewInternalClient", portMapping.getInternalClient()); + args.put("NewInternalPort", new UnsignedIntegerTwoBytes(portMapping.getInternalPort())); + args.put("NewLeaseDuration", new UnsignedIntegerFourBytes(portMapping.getLeaseDuration())); + args.put("NewEnabled", portMapping.isEnabled()); + args.put("NewRemoteHost", portMapping.getRemoteHost()); + args.put("NewPortMappingDescription", portMapping.getDescription()); + return args; + } + + @Override + public Void convert(final ActionInvocation response) { + return null; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ClingAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ClingAction.java new file mode 100644 index 0000000..e4c9b01 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/ClingAction.java @@ -0,0 +1,28 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.RemoteService; + +public interface ClingAction { + + ActionInvocation getActionInvocation(); + + T convert(ActionInvocation response); +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/DeletePortMappingAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/DeletePortMappingAction.java new file mode 100644 index 0000000..1df427e --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/DeletePortMappingAction.java @@ -0,0 +1,54 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import java.util.HashMap; +import java.util.Map; + +import org.chris.portmapper.model.PortMapping; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.types.UnsignedIntegerTwoBytes; + +public class DeletePortMappingAction extends AbstractClingAction { + + private final int externalPort; + private final String protocol; + private final String remoteHost; + + public DeletePortMappingAction(final RemoteService service, final PortMapping portMapping) { + super(service, "DeletePortMapping"); + this.externalPort = portMapping.getExternalPort(); + this.protocol = portMapping.getProtocol().getName(); + this.remoteHost = portMapping.getRemoteHost(); + } + + @Override + public Map getArgumentValues() { + final HashMap args = new HashMap<>(); + args.put("NewExternalPort", new UnsignedIntegerTwoBytes(externalPort)); + args.put("NewProtocol", protocol); + args.put("NewRemoteHost", remoteHost); + return args; + } + + @Override + public Void convert(final ActionInvocation response) { + return null; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetExternalIpAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetExternalIpAction.java new file mode 100644 index 0000000..39ad304 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetExternalIpAction.java @@ -0,0 +1,36 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.cling.action; + +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.RemoteService; + +public class GetExternalIpAction extends AbstractClingAction { + + public GetExternalIpAction(final RemoteService service) { + super(service, "GetExternalIPAddress"); + } + + @Override + public String convert(final ActionInvocation invocation) { + return (String) invocation.getOutput("NewExternalIPAddress").getValue(); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetPortMappingEntryAction.java b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetPortMappingEntryAction.java new file mode 100644 index 0000000..ce54368 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/cling/action/GetPortMappingEntryAction.java @@ -0,0 +1,75 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.cling.action; + +import java.util.Collections; +import java.util.Map; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.meta.RemoteDevice; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.meta.Service; +import org.fourthline.cling.model.types.UnsignedIntegerFourBytes; +import org.fourthline.cling.model.types.UnsignedIntegerTwoBytes; + +public class GetPortMappingEntryAction extends AbstractClingAction { + + private final int index; + + public GetPortMappingEntryAction(final Service service, final int index) { + super(service, "GetGenericPortMappingEntry"); + this.index = index; + } + + @Override + public Map getArgumentValues() { + return Collections. singletonMap("NewPortMappingIndex", new UnsignedIntegerTwoBytes(index)); + } + + @Override + public PortMapping convert(final ActionInvocation response) { + final Protocol protocol = Protocol.getProtocol(getStringValue(response, "NewProtocol")); + final String remoteHost = getStringValue(response, "NewRemoteHost"); + final int externalPort = getIntValue(response, "NewExternalPort"); + final String internalClient = getStringValue(response, "NewInternalClient"); + final int internalPort = getIntValue(response, "NewInternalPort"); + final String description = getStringValue(response, "NewPortMappingDescription"); + final boolean enabled = getBooleanValue(response, "NewEnabled"); + final long leaseDuration = getLongValue(response, "NewLeaseDuration"); + return new PortMapping(protocol, remoteHost, externalPort, internalClient, internalPort, description, enabled, + leaseDuration); + } + + private boolean getBooleanValue(final ActionInvocation response, final String argumentName) { + return (boolean) response.getOutput(argumentName).getValue(); + } + + protected int getIntValue(final ActionInvocation response, final String argumentName) { + return ((UnsignedIntegerTwoBytes) response.getOutput(argumentName).getValue()).getValue().intValue(); + } + + protected long getLongValue(final ActionInvocation response, final String argumentName) { + return ((UnsignedIntegerFourBytes) response.getOutput(argumentName).getValue()).getValue().longValue(); + } + + protected String getStringValue(final ActionInvocation response, final String argumentName) { + return (String) response.getOutput(argumentName).getValue(); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouter.java new file mode 100644 index 0000000..bd2bea6 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouter.java @@ -0,0 +1,110 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.dummy; + +import java.util.Collection; +import java.util.LinkedList; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.chris.portmapper.router.AbstractRouter; +import org.chris.portmapper.router.RouterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DummyRouter extends AbstractRouter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Collection mappings; + + public DummyRouter(final String name) { + super(name); + logger.debug("Created new DummyRouter"); + mappings = new LinkedList<>(); + mappings.add(new PortMapping(Protocol.TCP, "remoteHost1", 1, "internalClient1", 1, + getName() + ": dummy port mapping 1")); + mappings.add( + new PortMapping(Protocol.UDP, null, 2, "internalClient2", 2, getName() + ": dummy port mapping 2")); + mappings.add( + new PortMapping(Protocol.TCP, null, 3, "internalClient3", 3, getName() + ": dummy port mapping 3")); + } + + @Override + public void addPortMapping(final PortMapping mapping) { + logger.debug("Adding mapping " + mapping); + mappings.add(mapping); + } + + @Override + public void addPortMappings(final Collection mappingsToAdd) { + logger.debug("Adding {} mappings: {}", mappingsToAdd.size(), mappingsToAdd); + this.mappings.addAll(mappingsToAdd); + } + + @Override + public void disconnect() { + logger.debug("Disconnect"); + } + + @Override + public String getExternalIPAddress() { + return "DummyExternalIP"; + } + + @Override + public String getInternalHostName() { + return "DummyInternalHostName"; + } + + @Override + public int getInternalPort() { + return 42; + } + + @Override + public Collection getPortMappings() { + try { + logger.debug("Sleep 3s to simulate delay when fetching port mappings."); + Thread.sleep(3000); + } catch (final InterruptedException e) { + // ignore + } + return mappings; + } + + @Override + public void logRouterInfo() { + logger.info("DummyRouter " + getName()); + } + + @Override + public void removeMapping(final PortMapping mapping) { + mappings.remove(mapping); + } + + @Override + public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort) { + // ignore + } + + @Override + public String getLocalHostAddress() throws RouterException { + return "DummyLocalhostAddress"; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouterFactory.java b/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouterFactory.java new file mode 100644 index 0000000..45598d8 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/dummy/DummyRouterFactory.java @@ -0,0 +1,52 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.dummy; + +import java.util.LinkedList; +import java.util.List; + +import org.chris.portmapper.PortMapperApp; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; + +/** + * Router factory for testing without a real router. + */ +public class DummyRouterFactory extends AbstractRouterFactory { + + public DummyRouterFactory(final PortMapperApp app) { + super(app, "Dummy library"); + } + + @Override + protected List findRoutersInternal() throws RouterException { + final List routers = new LinkedList<>(); + routers.add(new DummyRouter("DummyRouter1")); + routers.add(new DummyRouter("DummyRouter2")); + return routers; + } + + @Override + protected IRouter connect(final String locationUrl) throws RouterException { + return new DummyRouter("DummyRouter @ " + locationUrl); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIPortMappingExtractor.java b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIPortMappingExtractor.java new file mode 100644 index 0000000..7a07a68 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIPortMappingExtractor.java @@ -0,0 +1,182 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.sbbi; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.router.RouterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sbbi.upnp.impls.InternetGatewayDevice; +import net.sbbi.upnp.messages.ActionResponse; +import net.sbbi.upnp.messages.UPNPResponseException; + +/** + * This class fetches all {@link PortMapping} from an {@link InternetGatewayDevice}. + */ +class SBBIPortMappingExtractor { + + private final Logger logger; + private final InternetGatewayDevice router; + private final Collection mappings; + private boolean moreEntries; + private int currentMappingNumber; + private int nullPortMappings; + + /** + * The maximum number of port mappings that we will try to retrieve from the router. + */ + private final int maxNumPortMappings; + + SBBIPortMappingExtractor(final InternetGatewayDevice router, final int maxNumPortMappings) { + this(router, maxNumPortMappings, LoggerFactory.getLogger(SBBIPortMappingExtractor.class)); + } + + SBBIPortMappingExtractor(final InternetGatewayDevice router, final int maxNumPortMappings, final Logger logger) { + this.router = router; + this.maxNumPortMappings = maxNumPortMappings; + this.logger = logger; + this.mappings = new LinkedList<>(); + this.moreEntries = true; + this.currentMappingNumber = 0; + this.nullPortMappings = 0; + } + + public Collection getPortMappings() throws RouterException { + + try { + + /* + * This is a little trick to get all port mappings. There is a method that gets the number of available port + * mappings (getNatMappingsCount()), but it seems, that this method just tries to get all port mappings and + * checks, if an error is returned. + * + * In order to speed this up, we will do the same here, but stop, when the first exception is thrown. + */ + + while (morePortMappingsAvailable()) { + logger.debug("Getting port mapping with entry number {}...", currentMappingNumber); + + try { + final ActionResponse response = router.getGenericPortMappingEntry(currentMappingNumber); + addResponse(response); + } catch (final UPNPResponseException e) { + handleUPNPResponseException(e); + } + + currentMappingNumber++; + } + + checkMaxNumPortMappingsReached(); + + } catch (final IOException e) { + throw new RouterException("Could not get NAT mappings: " + e.getMessage(), e); + } + + logger.debug("Found {} mappings, {} mappings returned as null.", mappings.size(), nullPortMappings); + return mappings; + } + + /** + * Check, if the max number of entries is reached and print a warning message. + */ + private void checkMaxNumPortMappingsReached() { + if (currentMappingNumber == maxNumPortMappings) { + logger.warn( + "Reached max number of port mappings to get ({}). Perhaps not all port mappings where retrieved.", + maxNumPortMappings); + } + } + + private boolean morePortMappingsAvailable() { + return moreEntries && currentMappingNumber < maxNumPortMappings; + } + + private void addResponse(final ActionResponse response) { + // Create a port mapping for the response. + if (response != null) { + final PortMapping newMapping = PortMapping.create(response); + if (logger.isTraceEnabled()) { + logger.trace("Got port mapping #{}: {}", currentMappingNumber, newMapping.getCompleteDescription()); + } + mappings.add(newMapping); + } else { + nullPortMappings++; + logger.trace("Got a null port mapping for number {} ({} so far)", currentMappingNumber, nullPortMappings); + } + } + + private void handleUPNPResponseException(final UPNPResponseException e) { + if (isNoMoreMappingsException(e)) { + moreEntries = false; + logger.debug( + "Got no port mapping for entry number {} (error code: {}, error description: {}). Stop getting more entries.", + currentMappingNumber, e.getDetailErrorCode(), e.getDetailErrorDescription()); + } else { + moreEntries = false; + logger.error("Got exception when fetching port mapping for entry number " + currentMappingNumber + + ". Stop getting more entries.", e); + } + } + + /** + * This method checks, if the error code of the given exception means, that no more mappings are available. + *

+ * The following error codes are recognized: + *

    + *
  • SpecifiedArrayIndexInvalid: 713
  • + *
  • NoSuchEntryInArray: 714
  • + *
  • Invalid Args: 402 (e.g. for DD-WRT, TP-LINK TL-R460 firmware 4.7.6 Build 100714 Rel.63134n)
  • + *
  • Other errors, e.g. "The reference to entity "T" must end with the ';' delimiter" or + * "Content is not allowed in prolog": 899 (e.g. ActionTec MI424-WR, Thomson TWG850-4U)
  • + *
+ * See bug reports + * + * + * @param e + * the exception to check + * @return true, if the given exception means, that no more port mappings are available, else + * false. + */ + private boolean isNoMoreMappingsException(final UPNPResponseException e) { + final int errorCode = e.getDetailErrorCode(); + switch (errorCode) { + case 713: + case 714: + case 402: + case 899: + return true; + + default: + return false; + } + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouter.java new file mode 100644 index 0000000..2fa279d --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouter.java @@ -0,0 +1,233 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.sbbi; + +import java.io.IOException; +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.chris.portmapper.PortMapperApp; +import org.chris.portmapper.Settings; +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.chris.portmapper.router.AbstractRouter; +import org.chris.portmapper.router.RouterException; +import org.chris.portmapper.util.EncodingUtilities; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sbbi.upnp.devices.UPNPRootDevice; +import net.sbbi.upnp.impls.InternetGatewayDevice; +import net.sbbi.upnp.messages.UPNPResponseException; + +/** + * This class represents a router device and provides methods for managing port mappings and getting information about + * the router. It useses the SBBI library's {@link InternetGatewayDevice}. + */ +public class SBBIRouter extends AbstractRouter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * The wrapped router device. + */ + final private InternetGatewayDevice router; + + /** + * The maximum number of port mappings that we will try to retrieve from the router. + */ + private final static int MAX_NUM_PORTMAPPINGS = 500; + + private final PortMapperApp app; + + SBBIRouter(final PortMapperApp app, final InternetGatewayDevice router) { + super(router.getIGDRootDevice().getModelName()); + this.app = app; + this.router = router; + } + + @Override + public String getExternalIPAddress() throws RouterException { + logger.debug("Get external IP address..."); + String ipAddress; + try { + ipAddress = router.getExternalIPAddress(); + } catch (final UPNPResponseException e) { + throw new RouterException("Could not get external IP", e); + } catch (final IOException e) { + throw new RouterException("Could not get external IP", e); + } + logger.info("Got external IP address " + ipAddress + " for router."); + return ipAddress; + } + + @Override + public String getInternalHostName() { + logger.debug("Get internal IP address..."); + final URL presentationURL = router.getIGDRootDevice().getPresentationURL(); + if (presentationURL == null) { + logger.warn("Did not get presentation url"); + return null; + } + final String ipAddress = presentationURL.getHost(); + logger.info("Got internal host name '{}' for router.", ipAddress); + return ipAddress; + } + + @Override + public int getInternalPort() { + logger.debug("Get internal port of router..."); + final URL presentationURL = router.getIGDRootDevice().getPresentationURL(); + // Presentation URL may be null in some situations. + if (presentationURL != null) { + final int presentationUrlPort = presentationURL.getPort(); + // https://sourceforge.net/tracker/?func=detail&aid=3198378&group_id=213879&atid=1027466 + // Some routers send an invalid presentationURL, in this case use + // URLBase. + if (presentationUrlPort > 0) { + logger.debug("Got valid internal port {} from presentation URL '{}'", presentationUrlPort, + presentationURL); + return presentationUrlPort; + } else { + logger.debug("Got invalid port {} from presentation url '{}'", presentationUrlPort, presentationURL); + } + } else { + logger.debug("Presentation url is null"); + } + final URL urlBase = router.getIGDRootDevice().getURLBase(); + final int urlBasePort = urlBase.getPort(); + logger.debug("Presentation URL is null or returns invalid port: using port {} of base url '{}'", urlBasePort, + urlBase); + + return urlBasePort; + } + + @Override + public Collection getPortMappings() throws RouterException { + return new SBBIPortMappingExtractor(router, MAX_NUM_PORTMAPPINGS).getPortMappings(); + } + + @Override + public void logRouterInfo() throws RouterException { + final Map info = new HashMap<>(); + final UPNPRootDevice rootDevice = router.getIGDRootDevice(); + info.put("friendlyName", rootDevice.getFriendlyName()); + info.put("manufacturer", rootDevice.getManufacturer()); + info.put("modelDescription", rootDevice.getModelDescription()); + info.put("modelName", rootDevice.getModelName()); + info.put("serialNumber", rootDevice.getSerialNumber()); + info.put("vendorFirmware", rootDevice.getVendorFirmware()); + + info.put("modelNumber", rootDevice.getModelNumber()); + info.put("modelURL", rootDevice.getModelURL()); + info.put("manufacturerURL", rootDevice.getManufacturerURL().toExternalForm()); + info.put("presentationURL", + rootDevice.getPresentationURL() != null ? rootDevice.getPresentationURL().toExternalForm() : null); + info.put("urlBase", rootDevice.getURLBase().toExternalForm()); + + final SortedSet sortedKeys = new TreeSet<>(info.keySet()); + + for (final String key : sortedKeys) { + final String value = info.get(key); + logger.info("Router Info: {} \t= {}", key, value); + } + + logger.info("def loc: {}", rootDevice.getDeviceDefLoc()); + logger.trace("def loc data: {}", rootDevice.getDeviceDefLocData()); + logger.info("icons: {}", rootDevice.getDeviceIcons()); + logger.info("device type: {}", rootDevice.getDeviceType()); + logger.info("direct parent: {}", rootDevice.getDirectParent()); + logger.info("disc udn: {}", rootDevice.getDiscoveryUDN()); + logger.info("disc usn: {}", rootDevice.getDiscoveryUSN()); + logger.info("udn: {}", rootDevice.getUDN()); + } + + private boolean addPortMapping(final String description, final Protocol protocol, final String remoteHost, + final int externalPort, final String internalClient, final int internalPort, final int leaseDuration) + throws RouterException { + + final String protocolString = protocol == Protocol.TCP ? "TCP" : "UDP"; + + final String encodedDescription = encodeIfNecessary(description); + + try { + final boolean success = router.addPortMapping(encodedDescription, null, internalPort, externalPort, + internalClient, leaseDuration, protocolString); + return success; + } catch (final IOException e) { + throw new RouterException("Could not add port mapping: " + e.getMessage(), e); + } catch (final UPNPResponseException e) { + throw new RouterException("Could not add port mapping: " + e.getMessage(), e); + } + } + + private String encodeIfNecessary(final String description) { + final Settings settings = app.getSettings(); + if (settings == null || settings.isUseEntityEncoding()) { + return EncodingUtilities.htmlEntityEncode(description); + } + return description; + } + + @Override + public void addPortMappings(final Collection mappings) throws RouterException { + for (final PortMapping portMapping : mappings) { + logger.info("Adding port mapping {}", portMapping); + addPortMapping(portMapping); + } + } + + @Override + public void addPortMapping(final PortMapping mapping) throws RouterException { + logger.info("Adding port mapping ()", mapping.getCompleteDescription()); + addPortMapping(mapping.getDescription(), mapping.getProtocol(), mapping.getRemoteHost(), + mapping.getExternalPort(), mapping.getInternalClient(), mapping.getInternalPort(), 0); + } + + @Override + public void removeMapping(final PortMapping mapping) throws RouterException { + removePortMapping(mapping.getProtocol(), mapping.getRemoteHost(), mapping.getExternalPort()); + } + + @Override + public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort) + throws RouterException { + final String protocolString = (protocol.equals(Protocol.TCP) ? "TCP" : "UDP"); + try { + router.deletePortMapping(remoteHost, externalPort, protocolString); + } catch (final IOException e) { + throw new RouterException("Could not remove port mapping", e); + } catch (final UPNPResponseException e) { + throw new RouterException("Could not remove port mapping", e); + } + } + + @Override + public void disconnect() { + // Nothing to do right now. + } + + public long getUpTime() { + // The SBBI library does not provide a method for getting the uptime. + return 0; + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouterFactory.java b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouterFactory.java new file mode 100644 index 0000000..7880e13 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/sbbi/SBBIRouterFactory.java @@ -0,0 +1,76 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.sbbi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.sbbi.upnp.impls.InternetGatewayDevice; + +import org.chris.portmapper.PortMapperApp; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; + +/** + * Router factory using the SBBI UPnP library. + */ +public class SBBIRouterFactory extends AbstractRouterFactory { + + /** + * The timeout in milliseconds for finding a router device. + */ + private final static int DISCOVERY_TIMEOUT = 5000; + + public SBBIRouterFactory(final PortMapperApp app) { + super(app, "SBBI UPnP lib"); + } + + @Override + protected List findRoutersInternal() throws RouterException { + + final InternetGatewayDevice[] devices; + try { + devices = InternetGatewayDevice.getDevices(DISCOVERY_TIMEOUT); + } catch (final IOException e) { + throw new RouterException("Could not find devices", e); + } + + if (devices == null || devices.length == 0) { + return Collections.emptyList(); + } + + final List routers = new ArrayList<>(devices.length); + + for (final InternetGatewayDevice device : devices) { + routers.add(new SBBIRouter(app, device)); + } + + return routers; + } + + @Override + protected IRouter connect(final String locationUrl) throws RouterException { + throw new UnsupportedOperationException("Direct connection is not implemented for SBBI library."); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouter.java b/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouter.java new file mode 100644 index 0000000..58176d5 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouter.java @@ -0,0 +1,182 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.weupnp; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.bitlet.weupnp.GatewayDevice; +import org.bitlet.weupnp.PortMappingEntry; +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.model.Protocol; +import org.chris.portmapper.router.AbstractRouter; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is an implements an {@link IRouter} using the weupnp library's {@link GatewayDevice}. + */ +public class WeUPnPRouter extends AbstractRouter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final GatewayDevice device; + + WeUPnPRouter(final GatewayDevice device) { + super(device.getFriendlyName()); + this.device = device; + } + + @Override + public void addPortMapping(final PortMapping mapping) throws RouterException { + try { + device.addPortMapping(mapping.getExternalPort(), mapping.getInternalPort(), mapping.getInternalClient(), + mapping.getProtocol().getName(), mapping.getDescription()); + } catch (final Exception e) { + throw new RouterException("Could not add portmapping", e); + } + } + + @Override + public void addPortMappings(final Collection mappings) throws RouterException { + for (final PortMapping mapping : mappings) { + this.addPortMapping(mapping); + } + } + + @Override + public void disconnect() { + // noting to do right now + } + + @Override + public String getExternalIPAddress() throws RouterException { + try { + return device.getExternalIPAddress(); + } catch (final Exception e) { + throw new RouterException("Could not get external IP address", e); + } + } + + @Override + public String getInternalHostName() { + final String url = device.getPresentationURL(); + if (url == null || url.trim().length() == 0) { + return null; + } + try { + return new URL(url).getHost(); + } catch (final MalformedURLException e) { + logger.warn("Could not get URL for internal host name '" + url + "'", e); + return url; + } + } + + @Override + public int getInternalPort() throws RouterException { + String url = device.getPresentationURL(); + if (url == null) { + url = device.getURLBase(); + logger.info("Presentation url is null: use url base '{}'", url); + } + if (url == null) { + throw new RouterException("Presentation URL and URL base are null"); + } + + try { + return new URL(url).getPort(); + } catch (final MalformedURLException e) { + throw new RouterException("Could not get internal port from URL '" + url + "'", e); + } + } + + @Override + public Collection getPortMappings() throws RouterException { + final Collection mappings = new LinkedList<>(); + boolean morePortMappings = true; + int index = 0; + while (morePortMappings) { + final PortMappingEntry entry = new PortMappingEntry(); + try { + logger.debug("Getting port mapping {}...", index); + if (!device.getGenericPortMappingEntry(index, entry)) { + throw new RuntimeException(); + } + logger.debug("Got port mapping {}: {}", index, entry); + } catch (final Exception e) { + morePortMappings = false; + logger.debug("Got an exception with message '{}‘ for index {}, stop getting more mappings", + e.getMessage(), index); + } + + if (entry.getProtocol() != null) { + final Protocol protocol = entry.getProtocol().equalsIgnoreCase("TCP") ? Protocol.TCP : Protocol.UDP; + final PortMapping m = new PortMapping(protocol, entry.getRemoteHost(), entry.getExternalPort(), + entry.getInternalClient(), entry.getInternalPort(), entry.getPortMappingDescription()); + mappings.add(m); + } else { + logger.debug("Got null port mapping for index {}", index); + } + index++; + } + return mappings; + } + + @Override + public void logRouterInfo() throws RouterException { + final Map info = new HashMap<>(); + info.put("friendlyName", device.getFriendlyName()); + info.put("manufacturer", device.getManufacturer()); + info.put("modelDescription", device.getModelDescription()); + + final SortedSet sortedKeys = new TreeSet<>(info.keySet()); + + for (final String key : sortedKeys) { + final String value = info.get(key); + logger.info("Router Info: {} \t= {}", key, value); + } + + logger.info("def loc: {}", device.getLocation()); + logger.info("device type: {}", device.getDeviceType()); + } + + @Override + public void removeMapping(final PortMapping mapping) throws RouterException { + this.removePortMapping(mapping.getProtocol(), mapping.getRemoteHost(), mapping.getExternalPort()); + } + + @Override + public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort) + throws RouterException { + try { + device.deletePortMapping(externalPort, protocol.getName()); + } catch (final Exception e) { + throw new RouterException("Could not delete port mapping", e); + } + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouterFactory.java b/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouterFactory.java new file mode 100644 index 0000000..3d992f3 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/router/weupnp/WeUPnPRouterFactory.java @@ -0,0 +1,92 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.router.weupnp; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.bitlet.weupnp.GatewayDevice; +import org.bitlet.weupnp.GatewayDiscover; +import org.chris.portmapper.PortMapperApp; +import org.chris.portmapper.router.AbstractRouterFactory; +import org.chris.portmapper.router.IRouter; +import org.chris.portmapper.router.RouterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A router factoring using the weupnp library. + */ +public class WeUPnPRouterFactory extends AbstractRouterFactory { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final GatewayDiscover discover = new GatewayDiscover(); + + public WeUPnPRouterFactory(final PortMapperApp app) { + super(app, "weupnp lib"); + } + + @Override + protected List findRoutersInternal() throws RouterException { + logger.debug("Searching for gateway devices..."); + final Map devices; + try { + devices = discover.discover(); + } catch (final Exception e) { + throw new RouterException("Could not discover a valid gateway device: " + e.getMessage(), e); + } + + if (devices == null || devices.size() == 0) { + return Collections.emptyList(); + } + + final List routers = new ArrayList<>(devices.size()); + for (final GatewayDevice device : devices.values()) { + routers.add(new WeUPnPRouter(device)); + } + return routers; + } + + @Override + protected IRouter connect(final String locationUrl) throws RouterException { + + final GatewayDevice device = new GatewayDevice(); + device.setLocation(locationUrl); + device.setSt("urn:schemas-upnp-org:device:InternetGatewayDevice:1"); + try { + device.setLocalAddress(InetAddress.getLocalHost()); + } catch (final UnknownHostException e) { + throw new RouterException("Could not get ip of localhost: " + e.getMessage(), e); + } + try { + device.loadDescription(); + } catch (final Exception e) { + throw new RouterException( + "Could not load description of device for location url " + locationUrl + " : " + e.getMessage(), e); + } + return new WeUPnPRouter(device); + } +} diff --git a/UPnP/src/main/java/org/chris/portmapper/util/EncodingUtilities.java b/UPnP/src/main/java/org/chris/portmapper/util/EncodingUtilities.java new file mode 100644 index 0000000..a4e0998 --- /dev/null +++ b/UPnP/src/main/java/org/chris/portmapper/util/EncodingUtilities.java @@ -0,0 +1,61 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * + */ +package org.chris.portmapper.util; + +import java.util.HashMap; +import java.util.Map; + +public class EncodingUtilities { + + private static Map knownEncodings; + + static { + knownEncodings = new HashMap<>(); + knownEncodings.put('<', "<"); + knownEncodings.put('>', ">"); + knownEncodings.put('&', "&"); + } + + /** + * Replace all special characters with their html entities. + * + * @param s + * the string in which to replace the special characters. + * @return the result of the replacement. + */ + public static String htmlEntityEncode(final String s) { + final StringBuffer buf = new StringBuffer(); + + for (int i = 0; i < s.length(); i++) { + final char c = s.charAt(i); + if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9') { + buf.append(c); + } else { + if (knownEncodings.containsKey(c)) { + buf.append(knownEncodings.get(c)); + } else { + buf.append("&#" + (int) c + ";"); + } + } + } + return buf.toString(); + } +} diff --git a/UPnP/src/main/resources/logback.xml b/UPnP/src/main/resources/logback.xml new file mode 100644 index 0000000..baec989 --- /dev/null +++ b/UPnP/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_de.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_de.properties new file mode 100644 index 0000000..10b5f89 --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_de.properties @@ -0,0 +1,26 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +title = Meine Aufgabe +description = Verbinden zum Router. +startMessage = Verbinde... +errorMessage = Fehler: Verbindung zu %s konnte nicht aufgebaut werden +finishedMessage = Verbindung zu %s aufgebaut + +updateAddresses = Adressen werden aktualisiert +updatePortMappings = Port Mappings werden aktualisiert \ No newline at end of file diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_en.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_en.properties new file mode 100644 index 0000000..f8c2087 --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_en.properties @@ -0,0 +1,26 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +title = My Task +description = Connect to router. +startMessage = Connecting to router +errorMessage = Error: Could not connect to router: %s +finishedMessage = Connected to router %s + +updateAddresses = Updating Addresses +updatePortMappings = Updating Port Mappings \ No newline at end of file diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_nb.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_nb.properties new file mode 100644 index 0000000..17007da --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/ConnectTask_nb.properties @@ -0,0 +1,26 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +title = Min Oppgave +description = Koble til ruter. +startMessage = Kobler til ruter... +errorMessage = Feil: Kunne ikke koble til ruter: %s +finishedMessage = Koblet til ruter %s + +updateAddresses = Oppdaterer adresser +updatePortMappings = Oppdaterer portoppf�ringer \ No newline at end of file diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_de.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_de.properties new file mode 100644 index 0000000..c41e58a --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_de.properties @@ -0,0 +1,231 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +Application.id=PortMapper +Application.title=PortMapper +Application.version=@VERSION_NUMBER@ + +Application.lookAndFeel=system + +mainFrame.title=${Application.title} ${Application.version} + +messages.error_getting_localhost_address=Fehler beim Ermitteln der lokalen IP-Addresse.\nBenutzen Sie den 'Hinzuf�gen...' Button um eine Weiterleitung zu erzeugen\nund geben Sie ihre IP-Addresse manuell ein. +messages.error=Fehler + +messages.select_router.title=Router ausw�hlen +messages.select_router.message=Es wurde mehr als ein Router gefunden.\nBitte w�hlen Sie einen aus. + +mainFrame.router.title=Router +mainFrame.router.not_connected=(nicht verbunden) +mainFrame.router.updating=(aktualisieren...) +mainFrame.router.external_address=Externe IP-Addresse +mainFrame.router.internal_address=Interne IP-Addresse +mainFrame.log_messages.title=Log-Meldungen +mainFrame.port_mappings.title=Portweiterleitungen + + +mainFrame.mappings.protocol=Protokoll +mainFrame.mappings.remote_host=Entfernter Host +mainFrame.mappings.external_port=Externer Port +mainFrame.mappings.internal_client=Interne Addresse +mainFrame.mappings.internal_port=Interner Port +mainFrame.mappings.description=Beschreibung + +mainFrame.port_mapping_presets.title=Vorlagen + +# Actions + +mainFrame.showAboutDialog.Action.text=&�ber... +mainFrame.showAboutDialog.Action.shortDescription=Zeigt das �ber... Fenster + +mainFrame.router.updateAddresses.Action.text=&Aktualisieren +mainFrame.router.updateAddresses.Action.shortDescription=Aktualisiere die IP-Addressen des Router + +mainFrame.router.info.Action.text=&Info +mainFrame.router.info.Action.shortDescription=Zeige Informationen �ber den Router an + +mainFrame.router.connect.Action.text=&Verbinden +mainFrame.router.connect.Action.shortDescription=Verbindung zum Router aufbauen + +mainFrame.router.disconnect.Action.text=&Trennen +mainFrame.router.disconnect.Action.shortDescription=Verbindung zum Router trennen + +mainFrame.router.copyInternalAddress.Action.text=Kopieren +mainFrame.router.copyInternalAddress.Action.shortDescription=Kopiere die interne Addresse des Routers in die Zwischenablage + +mainFrame.router.copyExternalAddress.Action.text=Kopieren +mainFrame.router.copyExternalAddress.Action.shortDescription=Kopiere die externe Addresse des Routers in die Zwischenablage + +mainFrame.mappings.update.Action.text=Aktualisiere +mainFrame.mappings.update.Action.shortDescription=Aktualisiere die Portweiterleitungen + +mainFrame.mappings.remove.Action.text=&Entfernen +mainFrame.mappings.remove.Action.shortDescription=Entferne die ausgew�hlte Portweiterleitungen + +mainFrame.preset_mappings.create.Action.text=&Hinzuf�gen... +mainFrame.preset_mappings.create.Action.shortDescription=F�ge eine neue Portweiterleitung hinzu + +mainFrame.preset_mappings.edit.Action.text=Bearbeiten +mainFrame.preset_mappings.edit.Action.shortDescription=Bearbeite die ausgew�hlte Vorlage + +mainFrame.preset_mappings.remove.Action.text=&L�schen +mainFrame.preset_mappings.remove.Action.shortDescription=L�sche die ausgew�hlte Vorlage + +mainFrame.preset_mappings.use.Action.text=&Benutze +mainFrame.preset_mappings.use.Action.shortDescription=Benutze die ausgew�hlte Vorlage + +mainFrame.portmapper.settings.Action.text=Einstellungen... +mainFrame.portmapper.settings.Action.shortDescription=Einstellungen von PortMapper �ndern + +# About dialog + +about_dialog.close.Action.text=Schlie�en +about_dialog.close.Action.shortDescription=Schlie�e den Dialog + +about_dialog.title=�ber ${Application.title} ${Application.version} + +about_dialog.label1.text=${Application.title} Version ${Application.version} +about_dialog.label2.text=Verwalten der Portweiterleitungen eines Routers mit Universal Plug and Play. +about_dialog.label3.text=Created by Christoph Pirkl +about_dialog.label4.text=Folgende Bibiliotheken wurden benutzt + + +about_dialog.tooltip.click_here=Hier klicken um die URL +about_dialog.tooltip.in_browser=in einem Browser zu �ffnen + +about_dialog.upnplib_label.label=SBBI UPNPLib +about_dialog.upnplib_label.url=https://sourceforge.net/projects/upnplibmobile/ +about_dialog.upnplib_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.upnplib_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.weupnp_label.label=weupnp +about_dialog.weupnp_label.url=http://code.google.com/p/weupnp/ +about_dialog.weupnp_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.weupnp_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.cling_label.label=Cling +about_dialog.cling_label.url=http://4thline.org/projects/cling/ +about_dialog.cling_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.cling_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.app_framework_label.label=Better Swing Application Framework +about_dialog.app_framework_label.url=https://kenai.com/projects/bsaf/pages/Home +about_dialog.app_framework_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.app_framework_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.slf4j_label.label=slf4j +about_dialog.slf4j_label.url=http://www.slf4j.org/ +about_dialog.slf4j_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.slf4j_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.logback_label.label=logback +about_dialog.logback_label.url=http://logback.qos.ch/ +about_dialog.logback_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.logback_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.miglayout_label.label=MiGLayout +about_dialog.miglayout_label.url=http://www.miglayout.com/ +about_dialog.miglayout_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.miglayout_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.label5.text=Besuchen Sie die ${Application.title} Homepage + +about_dialog.homepage_label.label=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.url=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.homepage_label.url} ${about_dialog.tooltip.in_browser} + +# Edit preset mapping dialog + + +preset_dialog.title=Bearbeiten der Vorlagen + +preset_dialog.add_port.Action.text=Hinzuf�gen +preset_dialog.add_port.Action.shortDescription=F�ge einen neuen Port zur Liste hinzu + +preset_dialog.add_port_range.Action.text=Bereich hinzuf�gen... +preset_dialog.add_port_range.Action.shortDescription=F�ge mehrere Ports zur Liste hinzu + +preset_dialog.remove_port.Action.text=Entfernen +preset_dialog.remove_port.Action.shortDescription=Entferne einen Port aus der Liste + +preset_dialog.save.Action.text=Speichern +preset_dialog.save.Action.shortDescription=Speichere die �nderungen an den Vorlagen + +preset_dialog.cancel.Action.text=Abbrechen +preset_dialog.cancel.Action.shortDescription=Die �nderungen an den Vorlagen nicht speichern + +preset_dialog.ports.title=Ports + +preset_dialog.ports.protocol=Protokoll +preset_dialog.ports.internal=Interner Port +preset_dialog.ports.external=Externer Port + +preset_dialog.preset.text=Vorlagen +preset_dialog.protocol.text=Protokoll +preset_dialog.description.text=Beschreibung +preset_dialog.protocol_tcp.text=TCP +preset_dialog.protocol_udp.text=UDP +preset_dialog.remote_host.text=Entfernter Host +preset_dialog.remote_host_empty_for_all.text=(leer f�r alle) +preset_dialog.external_port.text=Externer Port +preset_dialog.internal_client.text=Interner Client +preset_dialog.internal_client_use_local_host.text=Benutze localhost +preset_dialog.internal_port.text=Interner Port + +preset_dialog.error.title=Fehler +preset_dialog.error.no_description=Bitte eine Beschreibung eingeben. +preset_dialog.error.no_ports=Bitte mindestens einen Port hinzuf�gen. +preset_dialog.error.duplicate_name=Eine Vorlage mit dieser Beschreibung existiert bereits, bitte eine andere Beschreibung eingeben. + +# Add port range dialog + +add_port_range_dialog.title=Mehrere Ports hinzuf�gen + +add_port_range_dialog.cancel.Action.text=Abbrechen +add_port_range_dialog.cancel.Action.shortDescription=Ports nicht hinzuf�gen + +add_port_range_dialog.add.Action.text=Hinzuf�gen +add_port_range_dialog.add.Action.shortDescription=Ports zur Liste hinzuf�gen + +add_port_range_dialog.protocol.text=Protokoll +add_port_range_dialog.internal_ports_from.text=Interne Ports: +add_port_range_dialog.internal_ports_to.text=bis +add_port_range_dialog.external_ports_from.text=Externe Ports: +add_port_range_dialog.external_ports_to.text=bis + +add_port_range_dialog.external_equal_internal.text=Externe Ports sind identisch mit internen Ports +add_port_range_dialog.invalid_number.title=Ung�ltige Port-Nummer +add_port_range_dialog.invalid_number.message=Bitte geben Sie Zahlen von 1 bis 65535 ein. + +add_port_range_dialog.invalid_internal_range.title=Ung�ltiger Zahlenbereich f�r internen Port +add_port_range_dialog.invalid_internal_range.message=Der 'bis'-Port muss gr��er als der 'von'-Port sein. + +add_port_range_dialog.invalid_external_range.title=Ung�ltiger Zahlenbereich f�r externen Port +add_port_range_dialog.invalid_external_range.message=Der 'bis'-Port muss gr��er als der 'von'-Port sein. + +add_port_range_dialog.invalid_range_length.title=Ung�ltiger Bereich +add_port_range_dialog.invalid_range_length.message=Der Bereich der externen Ports muss genauso lang sein wie der Bereich der internen Ports. + + +# Settings dialog + +settings_dialog.title=UPnP PortMapper Einstellungen + +settings_dialog.save.Action.text=Speichern +settings_dialog.save.Action.shortDescription=Einstellungen speichern + +settings_dialog.cancel.Action.text=Abbrechen +settings_dialog.cancel.Action.shortDescription=Einstellungen nicht speichern + +settings_dialog.use_entity_encoding.text=Verwende Entity Encoding f�r Beschreibungen + +settings_dialog.log_level.text=Log Granularit�t +settings_dialog.upnp_lib.text=UPnP Bibliothek diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_en.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_en.properties new file mode 100644 index 0000000..12ef40f --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_en.properties @@ -0,0 +1,230 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +Application.id=PortMapper +Application.title=PortMapper +Application.version=@VERSION_NUMBER@ + +Application.lookAndFeel=system + +mainFrame.title=${Application.title} ${Application.version} + +messages.error_getting_localhost_address=Could not retrieve address of local host.\nPlease use 'Add...' button to create a mapping\nand enter address of local host manually. +messages.error=Error + +messages.select_router.title=Select router +messages.select_router.message=More than one router was found.\nPlease select one to connect to. + +mainFrame.router.title=Router +mainFrame.router.not_connected=(not connected) +mainFrame.router.updating=(updating) +mainFrame.router.external_address=External address +mainFrame.router.internal_address=Internal address +mainFrame.log_messages.title=Log messages +mainFrame.port_mappings.title=Port mappings + + +mainFrame.mappings.protocol=Protocol +mainFrame.mappings.remote_host=Remote Host +mainFrame.mappings.external_port=External Port +mainFrame.mappings.internal_client=Internal Client +mainFrame.mappings.internal_port=Internal Port +mainFrame.mappings.description=Description + +mainFrame.port_mapping_presets.title=Port mapping presets + +# Actions + +mainFrame.showAboutDialog.Action.text=&About... +mainFrame.showAboutDialog.Action.shortDescription=Show about dialog + +mainFrame.router.updateAddresses.Action.text=&Update +mainFrame.router.updateAddresses.Action.shortDescription=Update router addresses + +mainFrame.router.info.Action.text=&Info +mainFrame.router.info.Action.shortDescription=Display router information + +mainFrame.router.connect.Action.text=&Connect +mainFrame.router.connect.Action.shortDescription=Connect to router + +mainFrame.router.disconnect.Action.text=&Disconnect +mainFrame.router.disconnect.Action.shortDescription=Disconnect from router + +mainFrame.router.copyInternalAddress.Action.text=Copy +mainFrame.router.copyInternalAddress.Action.shortDescription=Copy internal address of router to clipboard + +mainFrame.router.copyExternalAddress.Action.text=Copy +mainFrame.router.copyExternalAddress.Action.shortDescription=Copy external address of router to clipboard + +mainFrame.mappings.update.Action.text=Update +mainFrame.mappings.update.Action.shortDescription=Update port mappings + +mainFrame.mappings.remove.Action.text=&Remove +mainFrame.mappings.remove.Action.shortDescription=Remove selected port mappings + +mainFrame.preset_mappings.create.Action.text=Create +mainFrame.preset_mappings.create.Action.shortDescription=Create a new port mapping preset + +mainFrame.preset_mappings.edit.Action.text=Edit +mainFrame.preset_mappings.edit.Action.shortDescription=Edit the selected port mapping preset + +mainFrame.preset_mappings.remove.Action.text=Delete +mainFrame.preset_mappings.remove.Action.shortDescription=Delete the selected port mapping preset + +mainFrame.preset_mappings.use.Action.text=Use +mainFrame.preset_mappings.use.Action.shortDescription=Use the selected port mapping preset + +mainFrame.portmapper.settings.Action.text=PortMapper Settings... +mainFrame.portmapper.settings.Action.shortDescription=Edit the settings of UPnP PortMapper + + +# About dialog + +about_dialog.close.Action.text=Close +about_dialog.close.Action.shortDescription=Close about dialog + +about_dialog.title=About ${Application.title} ${Application.version} + +about_dialog.label1.text=${Application.title} Version ${Application.version} +about_dialog.label2.text=Manage port mappings of a router using Universal Plug and Play. +about_dialog.label3.text=Created by Christoph Pirkl +about_dialog.label4.text=Using the following libraries + + +about_dialog.tooltip.click_here=Click here to open URL +about_dialog.tooltip.in_browser=in an external browser + +about_dialog.upnplib_label.label=SBBI UPNPLib +about_dialog.upnplib_label.url=https://sourceforge.net/projects/upnplibmobile/ +about_dialog.upnplib_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.upnplib_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.weupnp_label.label=weupnp +about_dialog.weupnp_label.url=http://code.google.com/p/weupnp/ +about_dialog.weupnp_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.weupnp_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.cling_label.label=Cling +about_dialog.cling_label.url=http://4thline.org/projects/cling/ +about_dialog.cling_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.cling_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.app_framework_label.label=Better Swing Application Framework +about_dialog.app_framework_label.url=https://kenai.com/projects/bsaf/pages/Home +about_dialog.app_framework_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.app_framework_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.slf4j_label.label=slf4j +about_dialog.slf4j_label.url=http://www.slf4j.org/ +about_dialog.slf4j_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.slf4j_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.logback_label.label=logback +about_dialog.logback_label.url=http://logback.qos.ch/ +about_dialog.logback_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.logback_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.miglayout_label.label=MiGLayout +about_dialog.miglayout_label.url=http://www.miglayout.com/ +about_dialog.miglayout_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.miglayout_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.label5.text=Visit our homepage at + +about_dialog.homepage_label.label=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.url=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.homepage_label.url} ${about_dialog.tooltip.in_browser} + +# Edit preset mapping dialog + +preset_dialog.title=Edit Port Mapping Preset + +preset_dialog.add_port.Action.text=Add +preset_dialog.add_port.Action.shortDescription=Add a new port to the list + +preset_dialog.add_port_range.Action.text=Add port range... +preset_dialog.add_port_range.Action.shortDescription=Add a range of ports to the list + +preset_dialog.remove_port.Action.text=Remove +preset_dialog.remove_port.Action.shortDescription=Remove selected port from the list + +preset_dialog.save.Action.text=Save +preset_dialog.save.Action.shortDescription=Save the changed port mapping preset + +preset_dialog.cancel.Action.text=Cancel +preset_dialog.cancel.Action.shortDescription=Do not save the changed port mapping preset + +preset_dialog.ports.title=Ports + +preset_dialog.ports.protocol=Protocol +preset_dialog.ports.internal=Internal Port +preset_dialog.ports.external=External Port + +preset_dialog.preset.text=Preset +preset_dialog.protocol.text=Protocol +preset_dialog.description.text=Description +preset_dialog.protocol_tcp.text=TCP +preset_dialog.protocol_udp.text=UDP +preset_dialog.remote_host.text=Remote Host +preset_dialog.remote_host_empty_for_all.text=(empty for all) +preset_dialog.external_port.text=External Port +preset_dialog.internal_client.text=Internal Client +preset_dialog.internal_client_use_local_host.text=Use local host +preset_dialog.internal_port.text=Internal Port + +preset_dialog.error.title=Error +preset_dialog.error.no_description=Please enter a description. +preset_dialog.error.no_ports=Please add at least one port mapping. +preset_dialog.error.duplicate_name=A preset with this already exists, please enter another description. + +# Add port range dialog + +add_port_range_dialog.title=Add port range + +add_port_range_dialog.cancel.Action.text=Cancel +add_port_range_dialog.cancel.Action.shortDescription=Do not add the port range + +add_port_range_dialog.add.Action.text=Add +add_port_range_dialog.add.Action.shortDescription=Add the port range to the list + +add_port_range_dialog.protocol.text=Protocol +add_port_range_dialog.internal_ports_from.text=Internal ports: +add_port_range_dialog.internal_ports_to.text=to +add_port_range_dialog.external_ports_from.text=External ports: +add_port_range_dialog.external_ports_to.text=to + +add_port_range_dialog.external_equal_internal.text=External ports are equal to internal ports +add_port_range_dialog.invalid_number.title=Invalid port number +add_port_range_dialog.invalid_number.message=Please enter numbers from 1 to 65535. + +add_port_range_dialog.invalid_internal_range.title=Invalid internal port range +add_port_range_dialog.invalid_internal_range.message=The 'to' internal port must be higher than the 'from' internal port. + +add_port_range_dialog.invalid_external_range.title=Invalid external port range +add_port_range_dialog.invalid_external_range.message=The 'to' external port must be higher than the 'from' external port. + +add_port_range_dialog.invalid_range_length.title=Invalid range length +add_port_range_dialog.invalid_range_length.message=The external port range must contain the same number of ports\nas the internal port range. + +# Settings dialog + +settings_dialog.title=UPnP PortMapper Settings + +settings_dialog.save.Action.text=Save +settings_dialog.save.Action.shortDescription=Save the settings + +settings_dialog.cancel.Action.text=Cancel +settings_dialog.cancel.Action.shortDescription=Do not save the settings + +settings_dialog.use_entity_encoding.text=Use entity encoding for port mapping names + +settings_dialog.log_level.text=Log level +settings_dialog.upnp_lib.text=UPnP library diff --git a/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_nb.properties b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_nb.properties new file mode 100644 index 0000000..6f79953 --- /dev/null +++ b/UPnP/src/main/resources/org/chris/portmapper/resources/PortMapperApp_nb.properties @@ -0,0 +1,177 @@ +# +# UPnP PortMapper - A tool for managing port forwardings via UPnP +# Copyright (C) 2015 Christoph Pirkl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +Application.id=PortMapper +Application.title=PortMapper +Application.version=@VERSION_NUMBER@ + +Application.lookAndFeel=system + +mainFrame.title=${Application.title} ${Application.version} + +messages.error_getting_localhost_address=Fant ikke adressen til den lokal verten.\nTrykk p� "Legg til"-knappen for � opprette en oppf�ring\nog manuelt legge in adressen til den lokale verten. +messages.error=Feil + +mainFrame.router.title=Ruter +mainFrame.router.not_connected=(ikke tilkoblet) +mainFrame.router.updating=(oppdaterer) +mainFrame.router.external_address=Ekstern adresse +mainFrame.router.internal_address=Intern adresse +mainFrame.log_messages.title=Loggf�rte beskjeder +mainFrame.port_mappings.title=Portoppf�ringer + + +mainFrame.mappings.protocol=Protokoll +mainFrame.mappings.remote_host=Ekstern vert +mainFrame.mappings.external_port=Ekstern port +mainFrame.mappings.internal_client=Intern klient +mainFrame.mappings.internal_port=Intern port +mainFrame.mappings.description=Beskrivelse + +mainFrame.port_mapping_presets.title=Portoppf�ringsmaler + +# Actions + +mainFrame.showAboutDialog.Action.text=&Om... +mainFrame.showAboutDialog.Action.shortDescription=Vis Om vinduet + +mainFrame.router.updateAddresses.Action.text=&Oppdater +mainFrame.router.updateAddresses.Action.shortDescription=Oppdater ruter adressen + +mainFrame.router.info.Action.text=&Info +mainFrame.router.info.Action.shortDescription=Vis ruter informasjon + +mainFrame.router.connect.Action.text=&Koble til +mainFrame.router.connect.Action.shortDescription=Koble til ruter + +mainFrame.router.disconnect.Action.text=&Koble fra +mainFrame.router.disconnect.Action.shortDescription=Koble fra ruter + +mainFrame.router.copyInternalAddress.Action.text=Kopier +mainFrame.router.copyInternalAddress.Action.shortDescription=Kopier ruterens interne adresse til utklippstavlen + +mainFrame.router.copyExternalAddress.Action.text=Kopier +mainFrame.router.copyExternalAddress.Action.shortDescription=Kopier ruterens Eksterne adresse til utklippstavlen + +mainFrame.mappings.update.Action.text=Oppdater +mainFrame.mappings.update.Action.shortDescription=Oppdater portoppf�ringer + +mainFrame.mappings.remove.Action.text=&Fjern +mainFrame.mappings.remove.Action.shortDescription=Fjern valgte portoppf�ringer + +mainFrame.preset_mappings.create.Action.text=Opprett +mainFrame.preset_mappings.create.Action.shortDescription=Opprett en ny portoppf�ringsmal + +mainFrame.preset_mappings.edit.Action.text=Rediger +mainFrame.preset_mappings.edit.Action.shortDescription=Rediger valgt portoppf�ringsmal + +mainFrame.preset_mappings.remove.Action.text=Slett +mainFrame.preset_mappings.remove.Action.shortDescription=Slett valgt portoppf�ringsmal + +mainFrame.preset_mappings.use.Action.text=Bruk +mainFrame.preset_mappings.use.Action.shortDescription=Bruk valgt portoppf�ringsmal + + +# About dialog + +about_dialog.close.Action.text=Lukk +about_dialog.close.Action.shortDescription=Lukk Om vinduet + +about_dialog.title=Om ${Application.title} ${Application.version} + +about_dialog.label1.text=${Application.title} versjon ${Application.version} +about_dialog.label2.text=Administer en ruters portoppf�ringer ved bruk av Universal Plug and Play +about_dialog.label3.text=Utviklet av Christoph Pirkl +about_dialog.label4.text=F�lgene biblioteker er i bruk + + +about_dialog.tooltip.click_here=Klikk her for � �pne adressen +about_dialog.tooltip.in_browser=i nettleseren + +about_dialog.upnplib_label.label=SBBI UPNPLib +about_dialog.upnplib_label.url=https://sourceforge.net/projects/upnplibmobile/ +about_dialog.upnplib_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.upnplib_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.weupnp_label.label=weupnp +about_dialog.weupnp_label.url=http://code.google.com/p/weupnp/ +about_dialog.weupnp_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.weupnp_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.cling_label.label=Cling +about_dialog.cling_label.url=http://4thline.org/projects/cling/ +about_dialog.cling_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.cling_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.app_framework_label.label=Better Swing Application Framework +about_dialog.app_framework_label.url=https://kenai.com/projects/bsaf/pages/Home +about_dialog.app_framework_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.app_framework_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.slf4j_label.label=slf4j +about_dialog.slf4j_label.url=http://www.slf4j.org/ +about_dialog.slf4j_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.slf4j_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.logback_label.label=logback +about_dialog.logback_label.url=http://logback.qos.ch/ +about_dialog.logback_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.logback_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.miglayout_label.label=MiGLayout +about_dialog.miglayout_label.url=http://www.miglayout.com/ +about_dialog.miglayout_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.miglayout_label.url} ${about_dialog.tooltip.in_browser} + +about_dialog.label5.text=Bes�k v�r hjemmeside p� + +about_dialog.homepage_label.label=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.url=https://github.com/kaklakariada/portmapper +about_dialog.homepage_label.toolTipText=${about_dialog.tooltip.click_here} ${about_dialog.homepage_label.url} ${about_dialog.tooltip.in_browser} + +# Edit preset mapping dialog + + +preset_dialog.title=Rediger portoppf�ringsmal + +preset_dialog.add_port.Action.text=Legg til +preset_dialog.add_port.Action.shortDescription=Legg til en ny port til listen + +preset_dialog.remove_port.Action.text=Fjern +preset_dialog.remove_port.Action.shortDescription=Fjern valgt port fra listen + + +preset_dialog.save.Action.text=Lagre +preset_dialog.save.Action.shortDescription=Lagre endringene i portoppf�ringsmalen + +preset_dialog.cancel.Action.text=Avbryt +preset_dialog.cancel.Action.shortDescription=Forkast forandringene gjort til portoppf�ringsmalen + +preset_dialog.ports.title=Porter + +preset_dialog.ports.protocol=Protokoll +preset_dialog.ports.internal=Intern port +preset_dialog.ports.external=Ekstern port + +preset_dialog.preset.text=Mal +preset_dialog.protocol.text=Protokoll +preset_dialog.description.text=Beskrivelse +preset_dialog.protocol_tcp.text=TCP +preset_dialog.protocol_udp.text=UDP +preset_dialog.remote_host.text=Ekstern vert +preset_dialog.remote_host_empty_for_all.text=(la st� tom for alle) +preset_dialog.external_port.text=Ekstern port +preset_dialog.internal_client.text=Intern klient +preset_dialog.internal_client_use_local_host.text=Bruk den lokale verten +preset_dialog.internal_port.text=Intern port + + +preset_dialog.error.title=Feil diff --git a/UPnP/src/test/java/org/chris/portmapper/router/sbbi/TestPortMappingExtractor.java b/UPnP/src/test/java/org/chris/portmapper/router/sbbi/TestPortMappingExtractor.java new file mode 100644 index 0000000..97f5a82 --- /dev/null +++ b/UPnP/src/test/java/org/chris/portmapper/router/sbbi/TestPortMappingExtractor.java @@ -0,0 +1,138 @@ +/** + * UPnP PortMapper - A tool for managing port forwardings via UPnP + * Copyright (C) 2015 Christoph Pirkl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.chris.portmapper.router.sbbi; + +import static java.util.Arrays.*; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; + +import java.io.IOException; +import java.util.HashSet; + +import org.chris.portmapper.model.PortMapping; +import org.chris.portmapper.router.RouterException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; + +import net.sbbi.upnp.impls.InternetGatewayDevice; +import net.sbbi.upnp.messages.ActionResponse; +import net.sbbi.upnp.messages.UPNPResponseException; + +/** + * Unit tests for {@link SBBIPortMappingExtractor}. + */ +public class TestPortMappingExtractor { + + @Mock + private InternetGatewayDevice routerMock; + @Mock + private Logger loggerMock; + private SBBIPortMappingExtractor portMappingExtractor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + portMappingExtractor = new SBBIPortMappingExtractor(routerMock, 5, loggerMock); + } + + @Test + public void allMappingsNull() throws RouterException, IOException, UPNPResponseException { + simulateUPNPException(5, 713); + assertEquals(0, portMappingExtractor.getPortMappings().size()); + verify(loggerMock, times(1)).warn(anyString(), anyInt()); + verify(loggerMock, never()).error(anyString()); + assertNumMappingsFound(0, 5); + } + + @Test + public void allMappingsNullMaxNumReached() throws RouterException { + assertEquals(0, portMappingExtractor.getPortMappings().size()); + verify(loggerMock, times(1)).warn(anyString(), anyInt()); + verify(loggerMock, never()).error(anyString()); + assertNumMappingsFound(0, 5); + } + + @Test + public void noMapping() throws RouterException, IOException, UPNPResponseException { + simulateUPNPException(0, 713); + assertEquals(0, portMappingExtractor.getPortMappings().size()); + assertNoWarningOrErrorLogged(); + assertNumMappingsFound(0, 0); + } + + @Test + public void wrongErrorCode() throws RouterException, IOException, UPNPResponseException { + simulateUPNPException(0, 42); + assertEquals(0, portMappingExtractor.getPortMappings().size()); + verify(loggerMock, never()).warn(anyString()); + verify(loggerMock, never()).error(anyString()); + verify(loggerMock, never()).warn(anyString(), any(Throwable.class)); + verify(loggerMock).error(anyString(), any(Throwable.class)); + assertNumMappingsFound(0, 0); + } + + @Test + public void oneMapping() throws RouterException, IOException, UPNPResponseException { + simulateMapping(0); + simulateUPNPException(1, 713); + assertEquals(1, portMappingExtractor.getPortMappings().size()); + assertNoWarningOrErrorLogged(); + assertNumMappingsFound(1, 0); + } + + private void assertNumMappingsFound(final int numFound, final int numNull) { + verify(loggerMock).debug("Found {} mappings, {} mappings returned as null.", numFound, numNull); + } + + private void assertNoWarningOrErrorLogged() { + verify(loggerMock, never()).warn(anyString()); + verify(loggerMock, never()).error(anyString()); + verify(loggerMock, never()).warn(anyString(), any(Throwable.class)); + verify(loggerMock, never()).error(anyString(), any(Throwable.class)); + } + + private void simulateMapping(final int mappingEntry) throws IOException, UPNPResponseException { + final ActionResponse response = mock(ActionResponse.class); + when(response.getOutActionArgumentNames()).thenReturn( + new HashSet(asList(PortMapping.MAPPING_ENTRY_ENABLED, PortMapping.MAPPING_ENTRY_EXTERNAL_PORT, + PortMapping.MAPPING_ENTRY_INTERNAL_CLIENT, PortMapping.MAPPING_ENTRY_INTERNAL_PORT, + PortMapping.MAPPING_ENTRY_LEASE_DURATION, PortMapping.MAPPING_ENTRY_PORT_MAPPING_DESCRIPTION, + PortMapping.MAPPING_ENTRY_PROTOCOL, PortMapping.MAPPING_ENTRY_REMOTE_HOST))); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_ENABLED)).thenReturn("1"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_EXTERNAL_PORT)).thenReturn("2"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_INTERNAL_CLIENT)).thenReturn("internal"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_INTERNAL_PORT)).thenReturn("3"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_LEASE_DURATION)).thenReturn("4"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_PORT_MAPPING_DESCRIPTION)) + .thenReturn("description"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_PROTOCOL)).thenReturn("TCP"); + when(response.getOutActionArgumentValue(PortMapping.MAPPING_ENTRY_REMOTE_HOST)).thenReturn("remote"); + when(routerMock.getGenericPortMappingEntry(mappingEntry)).thenReturn(response); + } + + private void simulateUPNPException(final int mappingEntry, final int errorCode) + throws IOException, UPNPResponseException { + when(routerMock.getGenericPortMappingEntry(mappingEntry)).thenThrow(new UPNPResponseException(errorCode, + "exception for entry " + mappingEntry + ", error code " + errorCode)); + } +}