From: Felix Dörre
Date: Sun, 1 Mar 2015 00:33:57 +0000 (+0100)
Subject: Merge remote-tracking branch 'origin/libs/scrypt/local'
X-Git-Url: https://code.wpia.club/?p=gigi.git;a=commitdiff_plain;h=ec24cf6925bb3729a644580ad4a9375d05883c62;hp=feb2865e64c20c40d1953f0d0cbc272a7723462e
Merge remote-tracking branch 'origin/libs/scrypt/local'
---
diff --git a/.classpath b/.classpath
index fb501163..cab9630e 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index eda95824..0c170e19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,13 @@
# Generated Stuff
*.manifest
+#OS stuff
+.DS_Store
+
/bin
+/bintest
+/binutil
+/work
+static.tar.gz
+
+/src/org/cacert/gigi/util/effective_tld_names.dat
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 00000000..5a0ad22d
--- /dev/null
+++ b/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..2a0716e8
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,296 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+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.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
+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=48
+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=48
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=80
+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=0
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=32
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=32
+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=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1
+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=true
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+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=80
+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=true
+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=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=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=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=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=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not 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=do not 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=do not 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=do not 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=do not 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=do not 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=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=8192
+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=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 00000000..4ef74ca7
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,66 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_cacert-gigi
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=1
+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=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+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=false
+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=true
+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/Gigi.MF b/Gigi.MF
new file mode 100644
index 00000000..614d595d
--- /dev/null
+++ b/Gigi.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: org.cacert.gigi.Launcher
+
diff --git a/LICENSE b/LICENSE
index d7f10513..eafe33ad 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,339 +1,4 @@
-GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+The code is licensed under GPLv2.
+Parts of the code carry a dual license under GPLv2+BSD based on the wishes of their authors.
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {description}
- Copyright (C) {year} {fullname}
-
- 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 2 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, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- {signature of Ty Coon}, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
+For detailed license information see "debian/copyright" ...
diff --git a/LICENSE.BSD b/LICENSE.BSD
new file mode 100644
index 00000000..b7bf085a
--- /dev/null
+++ b/LICENSE.BSD
@@ -0,0 +1,24 @@
+Copyright (c) 2015, CAcert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSE.GPL b/LICENSE.GPL
new file mode 100644
index 00000000..d7f10513
--- /dev/null
+++ b/LICENSE.GPL
@@ -0,0 +1,339 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
index a4c34252..8f4be3be 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,6 @@ Gigi
=================
Webserver Module for CAcert
+
+
+Contains source from jetty 9.1.0.RC0
diff --git a/build.xml b/build.xml
new file mode 100644
index 00000000..5d4634d5
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/.gitignore b/config/.gitignore
new file mode 100644
index 00000000..59f2d874
--- /dev/null
+++ b/config/.gitignore
@@ -0,0 +1,5 @@
+
+keystore.pkcs12
+cacerts.jks
+gigi.properties
+test.properties
diff --git a/config/gigi.properties.template b/config/gigi.properties.template
new file mode 100644
index 00000000..ef794f64
--- /dev/null
+++ b/config/gigi.properties.template
@@ -0,0 +1,14 @@
+host=127.0.0.1
+name.static=static.cacert.local
+name.secure=secure.cacert.local
+name.www=www.cacert.local
+name.api=api.cacert.local
+
+https.port=443
+http.port=80
+#emailProvider=org.cacert.gigi.email.Sendmail
+emailProvider=org.cacert.gigi.email.CommandlineEmailProvider
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://
+sql.user=
+sql.password=
diff --git a/config/test.properties.template b/config/test.properties.template
new file mode 100644
index 00000000..02cb4d49
--- /dev/null
+++ b/config/test.properties.template
@@ -0,0 +1,38 @@
+type=local
+serverPort.https=443
+serverPort.http=80
+mail=localhost:8474
+
+# ==== OR ===
+type=autonomous
+java=java -cp bin;/path/to/mysqlConnector.jar org.cacert.gigi.Launcher
+serverPort.https=4443
+serverPort.http=8098
+mailPort=8473
+
+
+
+
+# ==== ALL ===
+name.static=static.cacert.local
+name.secure=secure.cacert.local
+name.www=www.cacert.local
+name.api=api.cacert.local
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=
+
+
+domain.manage=http://you-installation-of-the/index.php
+domain.http=you-intstallation-for-the-textfiles
+domain.dnstest=the.dns.zone
+domain.testns=the.authorativ.ns.for.domain.dnstest
+domain.local=a.domain.that.resolves.to.localhost
+
+
+email.address=somemail@yourdomain.org
+email.password=somemails-imap-password
+email.imap=imap.yourdomain.org
+email.imap.user=somemail-imap-useraccount
+email.non-address=some-non-existent-domain@yourdomain.org
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644
index 00000000..5a7dbaa5
--- /dev/null
+++ b/debian/.gitignore
@@ -0,0 +1,6 @@
+*.debhelper
+*.substvars
+*.log
+cacert-gigi-setuid
+files
+cacert-gigi
diff --git a/debian/cacert-gigi-testing.cacert-gigi-signer.default b/debian/cacert-gigi-testing.cacert-gigi-signer.default
new file mode 100644
index 00000000..436a32a3
--- /dev/null
+++ b/debian/cacert-gigi-testing.cacert-gigi-signer.default
@@ -0,0 +1 @@
+START_DAEMON=0
diff --git a/debian/cacert-gigi-testing.cacert-gigi-signer.init b/debian/cacert-gigi-testing.cacert-gigi-signer.init
new file mode 100644
index 00000000..b041f0d8
--- /dev/null
+++ b/debian/cacert-gigi-testing.cacert-gigi-signer.init
@@ -0,0 +1,175 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: cacert-gigi-signer
+# Required-Start: $local_fs $network $remote_fs $syslog mysql
+# Required-Stop: $local_fs $network $remote_fs $syslog mysql
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description:
+# Description:
+# <...>
+# <...>
+### END INIT INFO
+
+# Author: unknown
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi-signer"
+NAME=cacert-gigi-signer
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.util.SimpleSigner"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -r "/usr/share/java/gigi.jar" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+if [ "$START_DAEMON" = "0" ]; then
+ echo "Not starting $NAME (as configured in /etc/default/$NAME)";
+ exit 0;
+fi
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ if [ ! -f /var/lib/cacert-gigi/config/gigi.properties ]; then
+ echo Missing signer-configfile
+ return 2
+ fi
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --startas $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon -b --start --quiet --pidfile $PIDFILE --make-pidfile -d $DIR --startas $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ #start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/cacert-gigi-testing.cacert-gigi.default b/debian/cacert-gigi-testing.cacert-gigi.default
new file mode 100644
index 00000000..436a32a3
--- /dev/null
+++ b/debian/cacert-gigi-testing.cacert-gigi.default
@@ -0,0 +1 @@
+START_DAEMON=0
diff --git a/debian/cacert-gigi-testing.cacert-gigi.init b/debian/cacert-gigi-testing.cacert-gigi.init
new file mode 100644
index 00000000..278f6907
--- /dev/null
+++ b/debian/cacert-gigi-testing.cacert-gigi.init
@@ -0,0 +1,175 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: cacert-gigi
+# Required-Start: $local_fs $network $remote_fs $syslog mysql
+# Required-Stop: $local_fs $network $remote_fs $syslog mysql
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description:
+# Description:
+# <...>
+# <...>
+### END INIT INFO
+
+# Author: unknown
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi"
+NAME=cacert-gigi
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.Launcher"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -r "/usr/share/java/gigi.jar" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+if [ "$START_DAEMON" = "0" ]; then
+ echo "Not starting $NAME (as configured in /etc/default/$NAME)";
+ exit 0;
+fi
+
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ if [ ! -f /etc/cacert/gigi/conf.tar ]; then
+ echo Missing gigi-configfile
+ exit 2
+ fi
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon -b --start --quiet --pidfile $PIDFILE -d $DIR --exec /usr/bin/gigi -- start-daemon \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/cacert-gigi-testing.docs b/debian/cacert-gigi-testing.docs
new file mode 100644
index 00000000..b43bf86b
--- /dev/null
+++ b/debian/cacert-gigi-testing.docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/cacert-gigi-testing.manpages b/debian/cacert-gigi-testing.manpages
new file mode 100644
index 00000000..3de344bc
--- /dev/null
+++ b/debian/cacert-gigi-testing.manpages
@@ -0,0 +1 @@
+debian/gigi.1
diff --git a/debian/cacert-gigi-testing.postinst b/debian/cacert-gigi-testing.postinst
new file mode 100644
index 00000000..3a7ec742
--- /dev/null
+++ b/debian/cacert-gigi-testing.postinst
@@ -0,0 +1,45 @@
+#!/bin/sh
+# postinst script for cacert-gigi
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `configure'
+# * `abort-upgrade'
+# * `abort-remove' `in-favour'
+#
+# * `abort-remove'
+# * `abort-deconfigure' `in-favour'
+# `removing'
+#
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+if [ -f /usr/share/debconf/confmodule ]; then
+ . /usr/share/debconf/confmodule
+fi
+
+gigi fetch-locales
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/cacert-gigi.cacert-gigi-signer.init b/debian/cacert-gigi.cacert-gigi-signer.init
new file mode 100644
index 00000000..7e6c085e
--- /dev/null
+++ b/debian/cacert-gigi.cacert-gigi-signer.init
@@ -0,0 +1,171 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: cacert-gigi-signer
+# Required-Start: $local_fs $network $remote_fs $syslog mysql
+# Required-Stop: $local_fs $network $remote_fs $syslog mysql
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description:
+# Description:
+# <...>
+# <...>
+### END INIT INFO
+
+# Author: unknown
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi-signer"
+NAME=cacert-gigi-signer
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.util.SimpleSigner"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ if [ ! -f /var/lib/cacert-gigi/config/gigi.properties ]; then
+ echo Missing signer-configfile
+ return 0
+ fi
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --startas $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon -b --start --quiet --pidfile $PIDFILE --make-pidfile -d $DIR --startas $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ #start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/cacert-gigi.cacert-gigi.init b/debian/cacert-gigi.cacert-gigi.init
new file mode 100644
index 00000000..d044355f
--- /dev/null
+++ b/debian/cacert-gigi.cacert-gigi.init
@@ -0,0 +1,170 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: cacert-gigi
+# Required-Start: $local_fs $network $remote_fs $syslog mysql
+# Required-Stop: $local_fs $network $remote_fs $syslog mysql
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description:
+# Description:
+# <...>
+# <...>
+### END INIT INFO
+
+# Author: unknown
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi"
+NAME=cacert-gigi
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.Launcher"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ if [ ! -f /etc/cacert-gigi/conf.tar ]; then
+ echo Missing gigi-configfile
+ exit 0
+ fi
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon -b --start --quiet --pidfile $PIDFILE -d $DIR --exec /usr/bin/gigi -- start-daemon \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/debian/cacert-gigi.docs b/debian/cacert-gigi.docs
new file mode 100644
index 00000000..b43bf86b
--- /dev/null
+++ b/debian/cacert-gigi.docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/cacert-gigi.manpages b/debian/cacert-gigi.manpages
new file mode 100644
index 00000000..3de344bc
--- /dev/null
+++ b/debian/cacert-gigi.manpages
@@ -0,0 +1 @@
+debian/gigi.1
diff --git a/debian/cacert-gigi.postinst b/debian/cacert-gigi.postinst
new file mode 100644
index 00000000..3a7ec742
--- /dev/null
+++ b/debian/cacert-gigi.postinst
@@ -0,0 +1,45 @@
+#!/bin/sh
+# postinst script for cacert-gigi
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `configure'
+# * `abort-upgrade'
+# * `abort-remove' `in-favour'
+#
+# * `abort-remove'
+# * `abort-deconfigure' `in-favour'
+# `removing'
+#
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+if [ -f /usr/share/debconf/confmodule ]; then
+ . /usr/share/debconf/confmodule
+fi
+
+gigi fetch-locales
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..cc57cffe
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+cacert-gigi (0.1) unstable; urgency=low
+
+ * Initial Release
+
+ -- CAcert Software Team Thu, 25 Sep 2014 03:19:20 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..ec635144
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..001f80e6
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,29 @@
+Source: cacert-gigi
+Section: java
+Priority: optional
+Maintainer: unknown
+Build-Depends: debhelper (>= 8.0.0), openjdk-8-jdk-gigi
+Standards-Version: 3.9.4
+Homepage: http://cacert.org
+#Vcs-Git: git://git.debian.org/collab-maint/cacert-gigi.git
+#Vcs-Browser: http://git.debian.org/?p=collab-maint/cacert-gigi.git;a=summary
+
+Package: cacert-gigi
+Architecture: all
+Depends: openjdk-8-jdk-gigi, cacert-gigi-setuid, ${shlibs:Depends}, ${misc:Depends}
+Conflicts: cacert-gigi-testing
+Description: CAcert Web-DB software.
+ This program is used to manage accounts and certificates.
+
+Package: cacert-gigi-testing
+Architecture: all
+Depends: openjdk-8-jdk-gigi, cacert-gigi-setuid, ${shlibs:Depends}, ${misc:Depends}
+Conflicts: cacert-gigi
+Description: CAcert Web-DB software testing version.
+ This program is the release to the testing server.
+
+Package: cacert-gigi-setuid
+Architecture: any
+Depends: openjdk-8-jdk-gigi, ${shlibs:Depends}, ${misc:Depends}
+Description: CAcert Web-DB software's setuid native library.
+ It is used to drop privilleges after allocating ports.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..c4549482
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,65 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: gigi
+Source:
+
+Files: *
+Copyright: 2014 CAcert Software Team
+License: GPL-2.0
+
+Files: debian/*
+Copyright: 2014 CAcert Software Team
+License: GPL-2.0
+
+Files: src/org/cacert/gigi/output/template
+Copyright: 2015 CAcert Software Team
+License: GPL-2.0 or BSD
+
+Files: src/org/cacert/gigi/localisation
+Copyright: 2015 CAcert Software Team
+License: GPL-2.0 or BSD
+
+Files: src/org/cacert/gigi/database
+Copyright: 2015 CAcert Software Team
+License: GPL-2.0 or BSD
+
+License: BSD
+ Copyright (c) 2015, CAcert
+ All rights reserved.
+ .
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+License: GPL-2.0
+ This package 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; only version 2 of the License.
+ .
+ This package 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
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
diff --git a/debian/gigi.1 b/debian/gigi.1
new file mode 100644
index 00000000..61b41b69
--- /dev/null
+++ b/debian/gigi.1
@@ -0,0 +1,49 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" (C) Copyright 2014 CAcert Software Team ,
+.\"
+.TH CACERT-GIGI 1 "September 25, 2014"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+cacert-gigi \- a starter for the CAcert-gigi system
+.SH SYNOPSIS
+.B cacert-gigi
+.RI {start|signer|reset-database|fetch-locales}
+.SH DESCRIPTION
+.B cacert-gigi
+is the starter for the CAcert-gigi system.
+.\" TeX users may be more comfortable with the \fB\fP and
+.\" \fI\fP escape sequences to invode bold face and italics,
+.\" respectively.
+.SH OPTIONS
+.TP
+.B debug
+Run the usual webdb (not forking) with opening jdwp on port 8000. You will need to pipe the config into this program.
+.TP
+.B fetch-locales
+Fetch all Translations from http://translations.cacert.org/
+.TP
+.B reset-database
+Delete the whole database contents, resetting it to default.
+.TP
+.B signer
+Run the test-replacement signer (not forking).
+.TP
+.B signer-conf
+Configure the (internal) signer and the "reset-database"-tool with the config from stdin.
+.TP
+.B start
+Run the usual webdb (not forking). You will need to pipe the config into this program.
+.TP
+.B start-daemon
+Run the usual webdb (forking). You will not need to pipe the config into this program. It reads the config from /etc/cacert/gigi/conf.tar
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..f0e56fb9
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,26 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
+
+build:
+ ant pack
+
+override_dh_auto_clean:
+ echo i dont clean
+
+override_dh_installinit:
+ dh_installinit --name=cacert-gigi
+ dh_installinit --name=cacert-gigi-signer
+
+override_dh_auto_build: build
+
+override_dh_auto_install:
+ DESTDIR=debian/cacert-gigi ant install
+ DESTDIR=debian/cacert-gigi-testing ant install-testing
+ DESTDIR=debian/cacert-gigi-setuid ant install-native
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 00000000..89ae9db8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 00000000..becafc37
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1 @@
+sampleData.sql
diff --git a/doc/TemplateSyntax.txt b/doc/TemplateSyntax.txt
new file mode 100644
index 00000000..ad8e09ae
--- /dev/null
+++ b/doc/TemplateSyntax.txt
@@ -0,0 +1,29 @@
+A template is constructed from a charstream. Everything that is not in "" to "?>" will be outputted directly. Text in these markers will be interpreted is template scripting syntax. The following strings are valid:
+
+General remarks:
+- $variablename: a variablename matches the regex [a-zA-Z0-9_-]
+Syntax:
+- =$variablename?> will output "variablename".
+ if "variablename" is an Outputable output this thing recursively.
+ else turn it into a String (Object.toString()) and output it.
+
+- =$!variablename?> will output the variable "variablename" but not HTML-escaped
+
+- =_This is free Text.?> will translate "This is free Text." into the users language, (escaped) and output it.
+ Text may not contain "?>".
+ If the text contains "$" or "!'" it is interpreted as "advanced replacement".
+ - ${variablename} is interpreted as "output this variable at this point"
+ - !'literal content' output "literal content" here and do not translate or escape. (literal content may not contain any of: {}'$ )
+ Then the whole text than also may not contain "{" and "}".
+
+- if($variable) { ?> ... } ?>
+ Output/execute the text until " } ?>" only if $variable is Boolean.TRUE (<=> !Boolean.FALSE) or not null.
+- if(...) { ?> ... } else { ?> ... } ?>
+
+- foreach($variable) { ?> ... } ?>
+ If $variable is an "IterableDataset"
+ Output/execute the text until " } ?>" repeated as $variable suggests.
+ Special variables that $variable defines can be used in the inner text.
+
+
+
\ No newline at end of file
diff --git a/doc/beforeYouStart.txt b/doc/beforeYouStart.txt
new file mode 100644
index 00000000..b51c5db6
--- /dev/null
+++ b/doc/beforeYouStart.txt
@@ -0,0 +1,15 @@
+Before you start using you might want to:
+
+- create a keypair for the server (scripts/generateKeys.sh)
+- create a truststore for the server (scripts/generateTruststore.sh)
+
+- download locales (util/ org.cacert.gigi.util.FetchLocales)
+- write your sql connection properties: config/gigi.properties.template -> config/gigi.properties
+- install "hosts" entries for the hosts you entered in "gigi.properties"
+ (be aware if you change the default ones you need to change the CN given in the certificates)
+
+- add the corresponding jdbc connector to your path.
+
+- on unix-like systems: to securely run on privileged ports <= 1024 build the native setuid library (run the makefile in natives/).
+ This expects JAVA_HOME to be set.
+
diff --git a/doc/exoticKeys.sh b/doc/exoticKeys.sh
new file mode 100644
index 00000000..caa4c769
--- /dev/null
+++ b/doc/exoticKeys.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+openssl ecparam -out ec.pem -name secp521r1 -genkey
+openssl req -new -key ec.pem -out ec.csr -subj "/CN=bla"
+
+openssl dsaparam -genkey 1024 -out dsa.pem
+openssl req -new -key dsa.pem -out dsa.csr -subj "/CN=bla"
+
diff --git a/doc/jenkinsJob/README.txt b/doc/jenkinsJob/README.txt
new file mode 100644
index 00000000..ec8e3372
--- /dev/null
+++ b/doc/jenkinsJob/README.txt
@@ -0,0 +1,12 @@
+you need the debian mysql-connector package (jar under /usr/share/java/mysql-connector.jar)
+
+
+a Password to the sql database to test in.
+
+/path/to/folder/with/junit/
+folder Containing:
+- junit.jar
+- org.hamcrest.core.jar
+
+
+Fill also all other variables that are marked with "$$$$"
diff --git a/doc/jenkinsJob/ci-tests-setup.txt b/doc/jenkinsJob/ci-tests-setup.txt
new file mode 100644
index 00000000..e51c280c
--- /dev/null
+++ b/doc/jenkinsJob/ci-tests-setup.txt
@@ -0,0 +1,16 @@
+-you need 4 domains resolving to the ci server (or localhost)
+preferably
+static.DOMAIN, secure.DOMAIN, www.DOMAIN and api.DOMAIN.
+enter them in the jenkins job to write them to "keys/config" and "config/test.properties"
+
+-you need credentials to an acessabible mysql database.
+make jenkins write them to "config/test.properties"
+
+-you need a dynamically managable dns zone.
+Write the zone name to "domain.dnstest" in "test.properties"
+and a manage script (see dyn-txt.php).
+- Put the url with password in "domain.manage"
+- Put the host with password in "domain.http"
+
+Setup with bind9:
+dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST example.org.
diff --git a/doc/jenkinsJob/config.xml b/doc/jenkinsJob/config.xml
new file mode 100644
index 00000000..505a7f13
--- /dev/null
+++ b/doc/jenkinsJob/config.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+ -1
+ 100
+ -1
+ -1
+
+ false
+
+
+
+
+ JAVA_HOME
+
+ /usr/lib/jvm/openjdk-8-jdk-gigi
+
+
+ BRANCH
+ The branch to build from.
+ master
+
+
+ TARGET
+ The target.
+
+
+ develop
+ release
+
+
+
+
+
+
+
+ 2
+
+
+ $$$$YOUR_REFERENCE_GIT_REPO$$$$
+
+
+
+
+ $BRANCH
+
+
+ false
+
+
+
+ cacert-gigi
+
+
+
+ true
+ false
+ false
+ false
+ Java 8 OpenJDK
+
+
+ @midnight
+ false
+
+
+ false
+
+
+ rm -f *.deb
+cd cacert-gigi
+cat <<EOT >keys/config
+DOMAIN=$$$$YOUR_LOOKUP_DOMAIN$$$$
+KEYSIZE=4096
+EOT
+cat <<EOT >config/test.properties
+type=autonomous
+java=/usr/lib/jvm/openjdk-8-jdk-gigi/bin/java -cp bintest:gigi-testing.jar:/usr/share/java/mysql-connector-java.jar -javaagent:/usr/share/java/jacocoagent.jar org.cacert.gigi.Launcher
+serverPort.https=4448
+serverPort.http=8098
+mailPort=8473
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=$$$$sql password$$$$
+name.static=static.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.secure=secure.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.www=www.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.api=api.$$$$YOUR_LOOKUP_DOMAIN$$$$
+
+domain.manage=http://$$$$YOUR_TESTSERVICE$$$$/dyn-txt.php?token=$$$$managementToken$$$$&
+domain.http=$$$$YOUR_TESTSERVICE_HTTP$$$$
+domain.dnstest=$$$$YOUR_TESTSERVICE_ZONE$$$$
+domain.testns=$$$$YOUR_TESTSERVICE_AUTH_NAMESERVER$$$$
+domain.local=test.$$$$YOUR_LOOKUP_DOMAIN$$$$
+
+email.address=$$$$YOUR_IMAP_EMAIL$$$$
+email.password=$$$$YOUR_IMAP_PASSWORD$$$$
+email.imap=$$$$YOUR_IMAP_SERVER$$$$
+email.imap.user=$$$$YOUR_IMAP_USERNAME$$$$
+email.non-address=$$$$IMAP_NON_EXISTENT_ADDRESS$$$$
+
+EOT
+
+
+
+
+ $TARGET generatecoco
+ -Dfile.encoding=UTF-8
+ cacert-gigi/build.xml
+ juintexec=$$$$JUNIT_PATH$$$$
+test_nic=$$$$YOUR_TESTSERVICE_NIC$$$$\n$$$$YOUR_LOOKUP_DOMAIN$$$$
+
+
+ cd cacert-gigi
+dpkg-buildpackage -b -us -uc
+
+
+
+
+ cacert-gigi/junit/*.xml
+ false
+
+
+
+ cacert-gigi/natives/*.so,cacert-gigi/gigi*.jar,cacert-gigi/gigi-linux_amd64.zip,*.deb
+ false
+ false
+
+
+
+ true
+
+
+
+
\ No newline at end of file
diff --git a/doc/jenkinsJob/dyn-txt.php b/doc/jenkinsJob/dyn-txt.php
new file mode 100644
index 00000000..c7b6cfec
--- /dev/null
+++ b/doc/jenkinsJob/dyn-txt.php
@@ -0,0 +1,65 @@
+ $ar){
+ if($nt < $time - 2){
+ unset($todelete[$nt]);
+ foreach($ar as $act){
+ if($act[0] == "http"){
+ unlink("cacert-{$act[1]}.txt");
+ } else if($act[0] == "dns") {
+ $dnscalls .= "update delete {$act[1]}._cacert._auth." . ZONENAME . " TXT\n";
+ }
+ }
+ }
+}
+file_put_contents("data.php", "");
+
+if($dnscalls != ""){
+ dnsAction($dnscalls);
+}
+
+function dnsAction($command) {
+ $call = "server localhost\n$command\nsend\nquit\n";
+
+ $nsupdate = popen("/usr/bin/nsupdate -k " . KEYNAME, 'w');
+ fwrite($nsupdate, $call);
+ $retval = pclose($nsupdate); // nsupdate doesn't return anything useful when called this way
+}
+
diff --git a/doc/scripts/.gitignore b/doc/scripts/.gitignore
new file mode 100644
index 00000000..ea4975ec
--- /dev/null
+++ b/doc/scripts/.gitignore
@@ -0,0 +1 @@
+/*.csr
diff --git a/doc/scripts/generateSomeCsrs.sh b/doc/scripts/generateSomeCsrs.sh
new file mode 100755
index 00000000..758342ab
--- /dev/null
+++ b/doc/scripts/generateSomeCsrs.sh
@@ -0,0 +1,7 @@
+cd `dirname $0`
+
+for i in {4..100}; do
+openssl req -newkey rsa:1024 -nodes -keyout /dev/null \
+ -out $i.csr -subj "/CN=tmp.cacert.local" \
+ -config ../../keys/selfsign.config;
+done
diff --git a/doc/scripts/getJetty.sh b/doc/scripts/getJetty.sh
new file mode 100644
index 00000000..cbec3669
--- /dev/null
+++ b/doc/scripts/getJetty.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+JETTY=C:/jars/jetty-distribution-9.1.0.RC0/org.eclipse.jetty.project
+
+pushd ../../lib/jetty/org/eclipse/jetty
+rm -fR *
+
+pushd $JETTY
+
+git checkout refs/tags/jetty-9.2.1.v20140609
+popd
+
+
+for package in http io security server servlet util
+do
+ cp -R $JETTY/jetty-$package/src/main/java/org/eclipse/jetty/$package .
+done
+
+cp -R $JETTY/jetty-http/src/main/resources/org/eclipse/jetty/http .
+
+
+
+cp -R $JETTY/jetty-$package/src/main/java/org/eclipse/jetty/$package .
+
+rm -R server/session/jmx
+rm -R server/handler/jmx
+rm -R server/jmx
+rm -R servlet/jmx
+
+rm util/log/JettyAwareLogger.java
+rm util/log/Slf4jLog.java
+rm server/Slf4jRequestLog.java
+popd
diff --git a/doc/scripts/gigi b/doc/scripts/gigi
new file mode 100755
index 00000000..763af7cb
--- /dev/null
+++ b/doc/scripts/gigi
@@ -0,0 +1,53 @@
+#!/bin/bash
+if [ "$JDBC_DRIVER" == "" ]
+then
+JDBC_DRIVER=/usr/share/java/mysql-connector-java.jar
+#echo "JDBC_DRIVER environment variable not set. Assumed path: $JDBC_DRIVER"
+fi
+if [ "$GIGI_EXEC" == "" ]
+then
+GIGI_EXEC=/usr/share/java/gigi.jar
+#echo "GIGI_EXEC environment variable not set. Assumed path: $GIGI_EXEC"
+fi
+
+cd /var/lib/cacert-gigi
+
+if [ "$1" == "start" ]
+then
+ java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.Launcher
+elif [ "$1" == "debug" ]
+then
+ java -cp $JDBC_DRIVER:$GIGI_EXEC -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 org.cacert.gigi.Launcher
+elif [ "$1" == "start-daemon" ]
+then
+ if [ ! -e /etc/cacert/gigi/conf.tar ]; then
+ echo "Config missing."
+ exit 1;
+ fi
+ java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.Launcher < /etc/cacert/gigi/conf.tar >> /var/log/cacert-gigi.log 2>&1 &
+ echo $! > /var/run/cacert-gigi.pid
+elif [ "$1" == "signer" ]
+then
+ java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.SimpleSigner
+elif [ "$1" == "reset-database" ]
+then
+ java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.DatabaseManager
+elif [ "$1" == "fetch-locales" ]
+then
+ java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.FetchLocales
+elif [ "$1" == "signer-conf" ]
+then
+ mkdir /var/lib/cacert-gigi/config
+ cd /var/lib/cacert-gigi/config
+ tar x gigi.properties
+else
+ echo "Usage: gigi "
+ echo "debug - starts gigi in debug mode (on port 8000, with config from stdin)"
+ echo "fetch-locales - (re)fetch the localisation"
+ echo "reset-database - resets the database"
+ echo "signer - starts the simple signer"
+ echo "signer-conf - extract config for simple signer (and reset-database) from the tar from stdin"
+ echo "start - starts gigi"
+ echo "start-daemon - starts gigi in background (using config from /etc/cacert/gigi/conf.tar)"
+
+fi
diff --git a/keys/.dirinfo b/keys/.dirinfo
new file mode 100644
index 00000000..c9087a3d
--- /dev/null
+++ b/keys/.dirinfo
@@ -0,0 +1,9 @@
+This directoy will contain keys created to test CAcert-gigi.
+generate them with doc/scripts/generateKeys.sh
+
+It may contain:
+
+testca/*
+testca.crt
+testca.key
+{api,secure,static,www}.{crt,key,csr,pkcs12}
diff --git a/keys/.gitignore b/keys/.gitignore
new file mode 100644
index 00000000..83df6203
--- /dev/null
+++ b/keys/.gitignore
@@ -0,0 +1,15 @@
+#generated keys
+*.crt
+*.csr
+*.key
+*.pkcs12
+*.ca
+*.crl
+csr
+crt
+signer_bundle.tar
+
+
+# user specific generation config
+config
+
diff --git a/keys/generateKeys.sh b/keys/generateKeys.sh
new file mode 100755
index 00000000..e9f75a73
--- /dev/null
+++ b/keys/generateKeys.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+# this script generates a set of sample keys
+DOMAIN="cacert.local"
+KEYSIZE=4096
+PRIVATEPW="changeit"
+
+[ -f config ] && . ./config
+
+
+rm -Rf *.csr *.crt *.key *.pkcs12 *.ca *.crl
+
+
+####### create various extensions files for the various certificate types ######
+cat < test_ca.cnf
+subjectKeyIdentifier = hash
+#extendedKeyUsage = critical
+basicConstraints = CA:true
+keyUsage = digitalSignature, nonRepudiation, keyCertSign, cRLSign
+TESTCA
+
+cat < test_subca.cnf
+subjectKeyIdentifier = hash
+#extendedKeyUsage = critical,
+basicConstraints = CA:true
+keyUsage = digitalSignature, nonRepudiation, keyCertSign, cRLSign
+TESTCA
+
+cat < test_req.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=serverAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+cat < test_reqClient.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=clientAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+cat < test_reqMail.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=emailProtection
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+
+genca(){ #subj, internalName
+
+ openssl genrsa -out $2.key ${KEYSIZE}
+ openssl req -new -key $2.key -out $2.csr -subj "$1/O=Test Environment CA Ltd./OU=Test Environment CAs"
+
+ mkdir $2.ca
+ mkdir $2.ca/newcerts
+ echo 01 > $2.ca/serial
+ touch $2.ca/db
+ echo unique_subject = no >$2.ca/db.attr
+
+}
+
+caSign(){ # key,ca,config
+ cd $2.ca
+ openssl ca -cert ../$2.crt -keyfile ../$2.key -in ../$1.csr -out ../$1.crt -days 365 -batch -config ../selfsign.config -extfile ../$3
+ cd ..
+}
+
+rootSign(){ # key
+ caSign $1 root test_subca.cnf
+}
+
+genserver(){ #key, subject, config
+ openssl genrsa -out $1.key ${KEYSIZE}
+ openssl req -new -key $1.key -out $1.csr -subj "$2" -config selfsign.config
+ caSign $1 env "$3"
+
+ openssl pkcs12 -inkey $1.key -in $1.crt -CAfile env.chain.crt -chain -name $1 -export -passout pass:changeit -out $1.pkcs12
+
+ keytool -importkeystore -noprompt -srckeystore $1.pkcs12 -destkeystore ../config/keystore.pkcs12 -srcstoretype pkcs12 -deststoretype pkcs12 -srcstorepass "changeit" -deststorepass "$PRIVATEPW"
+}
+
+
+# Generate the super Root CA
+genca "/CN=Cacert-gigi testCA" root
+openssl x509 -req -days 365 -in root.csr -signkey root.key -out root.crt -extfile test_ca.cnf
+
+# generate the various sub-CAs
+genca "/CN=Environment" env
+rootSign env
+genca "/CN=Unassured" unassured
+rootSign unassured
+genca "/CN=Assured" assured
+rootSign assured
+genca "/CN=Codesigning" codesign
+rootSign codesign
+genca "/CN=Timestamping" timestamp
+rootSign timestamp
+genca "/CN=Orga" orga
+rootSign orga
+genca "/CN=Orga sign" orgaSign
+rootSign orgaSign
+
+
+cat env.crt root.crt > env.chain.crt
+
+# generate orga-keys specific to gigi.
+# first the server keys
+genserver www "/CN=www.${DOMAIN}" test_req.cnf
+genserver secure "/CN=secure.${DOMAIN}" test_req.cnf
+genserver static "/CN=static.${DOMAIN}" test_req.cnf
+genserver api "/CN=api.${DOMAIN}" test_req.cnf
+
+genserver signer_client "/CN=CAcert signer handler 1" test_reqClient.cnf
+genserver signer_server "/CN=CAcert signer 1" test_req.cnf
+
+# then the email signing key
+genserver mail "/emailAddress=support@${DOMAIN}" test_reqMail.cnf
+
+keytool -list -keystore ../config/keystore.pkcs12 -storetype pkcs12 -storepass "$PRIVATEPW"
+
+rm test_ca.cnf test_subca.cnf test_req.cnf test_reqMail.cnf test_reqClient.cnf
+rm env.chain.crt
+
+cat root.crt env.crt > ca.crt
+tar cf signer_bundle.tar root.crt env.crt signer_client.crt signer_client.key signer_server.crt signer_server.key ca.crt
+rm ca.crt
diff --git a/keys/generateTruststore.sh b/keys/generateTruststore.sh
new file mode 100755
index 00000000..0c5aedc2
--- /dev/null
+++ b/keys/generateTruststore.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# this script imports the cacert root certs
+
+rm -f ../config/cacerts.jks
+
+#wget -N http://www.cacert.org/certs/root.crt
+#wget -N http://www.cacert.org/certs/class3.crt
+
+#keytool -importcert -keystore ../config/cacerts.jks -file root.crt -alias root -storepass "changeit" $1
+#keytool -importcert -keystore ../config/cacerts.jks -file class3.crt -alias class3 -storepass "changeit" $1
+
+function import(){
+ keytool -importcert -keystore ../config/cacerts.jks -file "$1.crt" -alias own -storepass "changeit" -alias "$1" $2
+}
+
+import root -noprompt
+import assured
+import unassured
+
+keytool -list -keystore ../config/cacerts.jks -storepass "changeit"
diff --git a/keys/selfsign.config b/keys/selfsign.config
new file mode 100644
index 00000000..2b0f5a75
--- /dev/null
+++ b/keys/selfsign.config
@@ -0,0 +1,39 @@
+[req]
+distinguished_name=dn
+#req_extensions=ext
+
+[dn]
+[ext]
+subjectAltName=
+
+[ca]
+default_ca=ca1
+
+[ca1]
+new_certs_dir=newcerts
+database=db
+serial=serial
+default_md=sha256
+email_in_dn=salat
+policy=ca1_pol
+#default_days=365
+x509_extensions = v3_ca
+
+
+
+[ v3_ca ]
+
+basicConstraints = critical, CA:FALSE
+keyUsage = critical, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage = clientAuth, serverAuth, nsSGC, msSGC
+
+
+[ca1_pol]
+commonName = optional
+subjectAltName = optional
+organizationName = optional
+organizationalUnitName = optional
+emailAddress = optional
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
diff --git a/lib/jetty/org/eclipse/jetty/http/DateGenerator.java b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java
new file mode 100644
index 00000000..3ecaad8c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ static final String[] DAYS =
+ { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ static final String[] MONTHS =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+ private static final ThreadLocal __dateGenerator =new ThreadLocal()
+ {
+ @Override
+ protected DateGenerator initialValue()
+ {
+ return new DateGenerator();
+ }
+ };
+
+
+ public final static String __01Jan1970=DateGenerator.formatDate(0);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public static String formatDate(long date)
+ {
+ return __dateGenerator.get().doFormatDate(date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static void formatCookieDate(StringBuilder buf, long date)
+ {
+ __dateGenerator.get().doFormatCookieDate(buf,date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static String formatCookieDate(long date)
+ {
+ StringBuilder buf = new StringBuilder(28);
+ formatCookieDate(buf, date);
+ return buf.toString();
+ }
+
+ private final StringBuilder buf = new StringBuilder(32);
+ private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public String doFormatDate(long date)
+ {
+ buf.setLength(0);
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ int century = year / 100;
+ year = year % 100;
+
+ int hours = gc.get(Calendar.HOUR_OF_DAY);
+ int minutes = gc.get(Calendar.MINUTE);
+ int seconds = gc.get(Calendar.SECOND);
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append(' ');
+ buf.append(MONTHS[month]);
+ buf.append(' ');
+ StringUtil.append2digits(buf, century);
+ StringUtil.append2digits(buf, year);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ return buf.toString();
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+ */
+ public void doFormatCookieDate(StringBuilder buf, long date)
+ {
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ year = year % 10000;
+
+ int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+ int seconds = epoch % 60;
+ epoch = epoch / 60;
+ int minutes = epoch % 60;
+ int hours = epoch / 60;
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append('-');
+ buf.append(MONTHS[month]);
+ buf.append('-');
+ StringUtil.append2digits(buf, year/100);
+ StringUtil.append2digits(buf, year%100);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/DateParser.java b/lib/jetty/org/eclipse/jetty/http/DateParser.java
new file mode 100644
index 00000000..1ede4cec
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/DateParser.java
@@ -0,0 +1,109 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ final static String __dateReceiveFmt[] =
+ {
+ "EEE, dd MMM yyyy HH:mm:ss zzz",
+ "EEE, dd-MMM-yy HH:mm:ss",
+ "EEE MMM dd HH:mm:ss yyyy",
+
+ "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+ "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+ "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+ "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+ "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+ "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+ "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+ };
+
+ public static long parseDate(String date)
+ {
+ return __dateParser.get().parse(date);
+ }
+
+ private static final ThreadLocal __dateParser =new ThreadLocal()
+ {
+ @Override
+ protected DateParser initialValue()
+ {
+ return new DateParser();
+ }
+ };
+
+ final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+ private long parse(final String dateVal)
+ {
+ for (int i = 0; i < _dateReceive.length; i++)
+ {
+ if (_dateReceive[i] == null)
+ {
+ _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+ _dateReceive[i].setTimeZone(__GMT);
+ }
+
+ try
+ {
+ Date date = (Date) _dateReceive[i].parseObject(dateVal);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+
+ if (dateVal.endsWith(" GMT"))
+ {
+ final String val = dateVal.substring(0, dateVal.length() - 4);
+
+ for (SimpleDateFormat element : _dateReceive)
+ {
+ try
+ {
+ Date date = (Date) element.parseObject(val);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+ }
+ return -1;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpContent.java b/lib/jetty/org/eclipse/jetty/http/HttpContent.java
new file mode 100644
index 00000000..ebae56d1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpContent.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** HttpContent.
+ *
+ *
+ */
+public interface HttpContent
+{
+ String getContentType();
+ String getLastModified();
+ ByteBuffer getIndirectBuffer();
+ ByteBuffer getDirectBuffer();
+ String getETag();
+ Resource getResource();
+ long getContentLength();
+ InputStream getInputStream() throws IOException;
+ ReadableByteChannel getReadableByteChannel() throws IOException;
+ void release();
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class ResourceAsHttpContent implements HttpContent
+ {
+ final Resource _resource;
+ final String _mimeType;
+ final int _maxBuffer;
+ final String _etag;
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType)
+ {
+ this(resource,mimeType,-1,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer)
+ {
+ this(resource,mimeType,maxBuffer,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag)
+ {
+ this(resource,mimeType,-1,etag);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag)
+ {
+ _resource=resource;
+ _mimeType=mimeType;
+ _maxBuffer=maxBuffer;
+ _etag=etag?resource.getWeakETag():null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentType()
+ {
+ return _mimeType;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getLastModified()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getDirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,true);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getETag()
+ {
+ return _etag;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getIndirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,false);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long getContentLength()
+ {
+ return _resource.length();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return _resource.getInputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return _resource.getReadableByteChannel();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Resource getResource()
+ {
+ return _resource;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void release()
+ {
+ _resource.close();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpCookie.java b/lib/jetty/org/eclipse/jetty/http/HttpCookie.java
new file mode 100644
index 00000000..1a2426b1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpCookie.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.concurrent.TimeUnit;
+
+public class HttpCookie
+{
+ private final String _name;
+ private final String _value;
+ private final String _comment;
+ private final String _domain;
+ private final long _maxAge;
+ private final String _path;
+ private final boolean _secure;
+ private final int _version;
+ private final boolean _httpOnly;
+ private final long _expiration;
+
+ public HttpCookie(String name, String value)
+ {
+ this(name, value, -1);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path)
+ {
+ this(name, value, domain, path, -1, false, false);
+ }
+
+ public HttpCookie(String name, String value, long maxAge)
+ {
+ this(name, value, null, null, maxAge, false, false);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
+ {
+ this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
+ {
+ _name = name;
+ _value = value;
+ _domain = domain;
+ _path = path;
+ _maxAge = maxAge;
+ _httpOnly = httpOnly;
+ _secure = secure;
+ _comment = comment;
+ _version = version;
+ _expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
+ }
+
+ /**
+ * @return the cookie name
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * @return the cookie value
+ */
+ public String getValue()
+ {
+ return _value;
+ }
+
+ /**
+ * @return the cookie comment
+ */
+ public String getComment()
+ {
+ return _comment;
+ }
+
+ /**
+ * @return the cookie domain
+ */
+ public String getDomain()
+ {
+ return _domain;
+ }
+
+ /**
+ * @return the cookie max age in seconds
+ */
+ public long getMaxAge()
+ {
+ return _maxAge;
+ }
+
+ /**
+ * @return the cookie path
+ */
+ public String getPath()
+ {
+ return _path;
+ }
+
+ /**
+ * @return whether the cookie is valid for secure domains
+ */
+ public boolean isSecure()
+ {
+ return _secure;
+ }
+
+ /**
+ * @return the cookie version
+ */
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ /**
+ * @return whether the cookie is valid for the http protocol only
+ */
+ public boolean isHttpOnly()
+ {
+ return _httpOnly;
+ }
+
+ /**
+ * @param timeNanos the time to check for cookie expiration, in nanoseconds
+ * @return whether the cookie is expired by the given time
+ */
+ public boolean isExpired(long timeNanos)
+ {
+ return _expiration >= 0 && timeNanos >= _expiration;
+ }
+
+ /**
+ * @return a string representation of this cookie
+ */
+ public String asString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getName()).append("=").append(getValue());
+ if (getDomain() != null)
+ builder.append(";$Domain=").append(getDomain());
+ if (getPath() != null)
+ builder.append(";$Path=").append(getPath());
+ return builder.toString();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpField.java b/lib/jetty/org/eclipse/jetty/http/HttpField.java
new file mode 100644
index 00000000..50f29b1e
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpField.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+
+/* ------------------------------------------------------------ */
+/** A HTTP Field
+ */
+public class HttpField
+{
+ private final HttpHeader _header;
+ private final String _name;
+ private final String _value;
+
+ public HttpField(HttpHeader header, String name, String value)
+ {
+ _header = header;
+ _name = name;
+ _value = value;
+ }
+
+ public HttpField(HttpHeader header, String value)
+ {
+ this(header,header.asString(),value);
+ }
+
+ public HttpField(HttpHeader header, HttpHeaderValue value)
+ {
+ this(header,header.asString(),value.asString());
+ }
+
+ public HttpField(String name, String value)
+ {
+ this(HttpHeader.CACHE.get(name),name,value);
+ }
+
+ public HttpHeader getHeader()
+ {
+ return _header;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+ @Override
+ public String toString()
+ {
+ String v=getValue();
+ return getName() + ": " + (v==null?"":v);
+ }
+
+ public boolean isSame(HttpField field)
+ {
+ if (field==null)
+ return false;
+ if (field==this)
+ return true;
+ if (_header!=null && _header==field.getHeader())
+ return true;
+ if (_name.equalsIgnoreCase(field.getName()))
+ return true;
+ return false;
+ }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpFields.java b/lib/jetty/org/eclipse/jetty/http/HttpFields.java
new file mode 100644
index 00000000..d4742274
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpFields.java
@@ -0,0 +1,784 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
+ * This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ *
+ *
The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable
+{
+ private static final Logger LOG = Log.getLogger(HttpFields.class);
+ private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
+ public final static String __separators = ", \t";
+
+ private final ArrayList _fields = new ArrayList<>(20);
+
+ /**
+ * Constructor.
+ */
+ public HttpFields()
+ {
+ }
+
+ /**
+ * Get Collection of header names.
+ */
+ public Collection getFieldNamesCollection()
+ {
+ final Set list = new HashSet<>(_fields.size());
+ for (HttpField f : _fields)
+ {
+ if (f!=null)
+ list.add(f.getName());
+ }
+ return list;
+ }
+
+ /**
+ * Get enumeration of header _names. Returns an enumeration of strings representing the header
+ * _names for this request.
+ */
+ public Enumeration getFieldNames()
+ {
+ return Collections.enumeration(getFieldNamesCollection());
+ }
+
+ public int size()
+ {
+ return _fields.size();
+ }
+
+ /**
+ * Get a Field by index.
+ * @return A Field value or null if the Field value has not been set
+ *
+ */
+ public HttpField getField(int i)
+ {
+ return _fields.get(i);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _fields.iterator();
+ }
+
+ public HttpField getField(HttpHeader header)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==header)
+ return f;
+ }
+ return null;
+ }
+
+ public HttpField getField(String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return f;
+ }
+ return null;
+ }
+
+ public boolean contains(HttpHeader header, String value)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==header && contains(f,value))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(String name, String value)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name) && contains(f,value))
+ return true;
+ }
+ return false;
+ }
+
+ private boolean contains(HttpField field,String value)
+ {
+ String v = field.getValue();
+ if (v==null)
+ return false;
+
+ if (value.equalsIgnoreCase(v))
+ return true;
+
+ String[] split = __splitter.split(v);
+ for (int i = 0; split!=null && i < split.length; i++)
+ {
+ if (value.equals(split[i]))
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean containsKey(String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return true;
+ }
+ return false;
+ }
+
+ public String getStringField(HttpHeader header)
+ {
+ return getStringField(header.asString());
+ }
+
+ public String get(HttpHeader header)
+ {
+ return getStringField(header.asString());
+ }
+
+ public String get(String header)
+ {
+ return getStringField(header);
+ }
+
+ /**
+ * @return the value of a field, or null if not found. For multiple fields of the same name,
+ * only the first is returned.
+ * @param name the case-insensitive field name
+ */
+ public String getStringField(String name)
+ {
+ HttpField field = getField(name);
+ return field==null?null:field.getValue();
+ }
+
+ /**
+ * Get multi headers
+ *
+ * @return List the values
+ * @param name the case-insensitive field name
+ */
+ public List getValuesList(String name)
+ {
+ final List list = new ArrayList<>();
+ for (HttpField f : _fields)
+ if (f.getName().equalsIgnoreCase(name))
+ list.add(f.getValue());
+ return list;
+ }
+
+ /**
+ * Get multi headers
+ *
+ * @return Enumeration of the values
+ * @param name the case-insensitive field name
+ */
+ public Enumeration getValues(final String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ final HttpField f = _fields.get(i);
+
+ if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
+ {
+ final int first=i;
+ return new Enumeration()
+ {
+ HttpField field=f;
+ int i = first+1;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (field==null)
+ {
+ while (i<_fields.size())
+ {
+ field=_fields.get(i++);
+ if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+ return true;
+ }
+ field=null;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (hasMoreElements())
+ {
+ String value=field.getValue();
+ field=null;
+ return value;
+ }
+ throw new NoSuchElementException();
+ }
+
+ };
+ }
+ }
+
+ List empty=Collections.emptyList();
+ return Collections.enumeration(empty);
+ }
+
+ /**
+ * Get multi field values with separator. The multiple values can be represented as separate
+ * headers of the same name, or by a single header using the separator(s), or a combination of
+ * both. Separators may be quoted.
+ *
+ * @param name the case-insensitive field name
+ * @param separators String of separators.
+ * @return Enumeration of the values, or null if no such header.
+ */
+ public Enumeration getValues(String name, final String separators)
+ {
+ final Enumeration e = getValues(name);
+ if (e == null)
+ return null;
+ return new Enumeration()
+ {
+ QuotedStringTokenizer tok = null;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (tok != null && tok.hasMoreElements()) return true;
+ while (e.hasMoreElements())
+ {
+ String value = e.nextElement();
+ if (value!=null)
+ {
+ tok = new QuotedStringTokenizer(value, separators, false, false);
+ if (tok.hasMoreElements()) return true;
+ }
+ }
+ tok = null;
+ return false;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (!hasMoreElements()) throw new NoSuchElementException();
+ String next = (String) tok.nextElement();
+ if (next != null) next = next.trim();
+ return next;
+ }
+ };
+ }
+
+ public void put(HttpField field)
+ {
+ boolean put=false;
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.isSame(field))
+ {
+ if (put)
+ _fields.remove(i);
+ else
+ {
+ _fields.set(i,field);
+ put=true;
+ }
+ }
+ }
+ if (!put)
+ _fields.add(field);
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(String name, String value)
+ {
+ if (value == null)
+ remove(name);
+ else
+ put(new HttpField(name, value));
+ }
+
+ public void put(HttpHeader header, HttpHeaderValue value)
+ {
+ put(header,value.toString());
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param header the header name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(HttpHeader header, String value)
+ {
+ if (value == null)
+ remove(header);
+ else
+ put(new HttpField(header, value));
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param list the List value of the field. If null the field is cleared.
+ */
+ public void put(String name, List list)
+ {
+ remove(name);
+ for (String v : list)
+ if (v!=null)
+ add(name,v);
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param name the name of the field
+ * @param value the value of the field.
+ * @exception IllegalArgumentException If the name is a single valued field and already has a
+ * value.
+ */
+ public void add(String name, String value) throws IllegalArgumentException
+ {
+ if (value == null)
+ return;
+
+ HttpField field = new HttpField(name, value);
+ _fields.add(field);
+ }
+
+ public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
+ {
+ add(header,value.toString());
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param header the header
+ * @param value the value of the field.
+ * @exception IllegalArgumentException
+ */
+ public void add(HttpHeader header, String value) throws IllegalArgumentException
+ {
+ if (value == null) throw new IllegalArgumentException("null value");
+
+ HttpField field = new HttpField(header, value);
+ _fields.add(field);
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ */
+ public HttpField remove(HttpHeader name)
+ {
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==name)
+ return _fields.remove(i);
+ }
+ return null;
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ */
+ public HttpField remove(String name)
+ {
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return _fields.remove(i);
+ }
+ return null;
+ }
+
+ /**
+ * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+ * case of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ * @exception NumberFormatException If bad long found
+ */
+ public long getLongField(String name) throws NumberFormatException
+ {
+ HttpField field = getField(name);
+ return field==null?-1L:StringUtil.toLong(field.getValue());
+ }
+
+ /**
+ * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+ * of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ */
+ public long getDateField(String name)
+ {
+ HttpField field = getField(name);
+ if (field == null)
+ return -1;
+
+ String val = valueParameters(field.getValue(), null);
+ if (val == null)
+ return -1;
+
+ final long date = DateParser.parseDate(val);
+ if (date==-1)
+ throw new IllegalArgumentException("Cannot convert date: " + val);
+ return date;
+ }
+
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(HttpHeader name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(String name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(HttpHeader name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void addDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ add(name,d);
+ }
+
+ @Override
+ public String
+ toString()
+ {
+ try
+ {
+ StringBuilder buffer = new StringBuilder();
+ for (HttpField field : _fields)
+ {
+ if (field != null)
+ {
+ String tmp = field.getName();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append(": ");
+ tmp = field.getValue();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append("\r\n");
+ }
+ }
+ buffer.append("\r\n");
+ return buffer.toString();
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return e.toString();
+ }
+ }
+
+ /**
+ * Clear the header.
+ */
+ public void clear()
+ {
+ _fields.clear();
+ }
+
+ public void add(HttpField field)
+ {
+ _fields.add(field);
+ }
+
+
+
+ /**
+ * Add fields from another HttpFields instance. Single valued fields are replaced, while all
+ * others are added.
+ *
+ * @param fields the fields to add
+ */
+ public void add(HttpFields fields)
+ {
+ if (fields == null) return;
+
+ Enumeration e = fields.getFieldNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ Enumeration values = fields.getValues(name);
+ while (values.hasMoreElements())
+ add(name, values.nextElement());
+ }
+ }
+
+ /**
+ * Get field value parameters. Some field values can have parameters. This method separates the
+ * value from the parameters and optionally populates a map with the parameters. For example:
+ *
+ *
+ *
+ * FieldName : Value ; param1=val1 ; param2=val2
+ *
+ *
+ *
+ * @param value The Field value, possibly with parameteres.
+ * @param parameters A map to populate with the parameters, or null
+ * @return The value.
+ */
+ public static String valueParameters(String value, Map parameters)
+ {
+ if (value == null) return null;
+
+ int i = value.indexOf(';');
+ if (i < 0) return value;
+ if (parameters == null) return value.substring(0, i).trim();
+
+ StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+ while (tok1.hasMoreTokens())
+ {
+ String token = tok1.nextToken();
+ StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+ if (tok2.hasMoreTokens())
+ {
+ String paramName = tok2.nextToken();
+ String paramVal = null;
+ if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+ parameters.put(paramName, paramVal);
+ }
+ }
+
+ return value.substring(0, i).trim();
+ }
+
+ private static final Float __one = new Float("1.0");
+ private static final Float __zero = new Float("0.0");
+ private static final Trie __qualities = new ArrayTernaryTrie<>();
+ static
+ {
+ __qualities.put("*", __one);
+ __qualities.put("1.0", __one);
+ __qualities.put("1", __one);
+ __qualities.put("0.9", new Float("0.9"));
+ __qualities.put("0.8", new Float("0.8"));
+ __qualities.put("0.7", new Float("0.7"));
+ __qualities.put("0.66", new Float("0.66"));
+ __qualities.put("0.6", new Float("0.6"));
+ __qualities.put("0.5", new Float("0.5"));
+ __qualities.put("0.4", new Float("0.4"));
+ __qualities.put("0.33", new Float("0.33"));
+ __qualities.put("0.3", new Float("0.3"));
+ __qualities.put("0.2", new Float("0.2"));
+ __qualities.put("0.1", new Float("0.1"));
+ __qualities.put("0", __zero);
+ __qualities.put("0.0", __zero);
+ }
+
+ public static Float getQuality(String value)
+ {
+ if (value == null) return __zero;
+
+ int qe = value.indexOf(";");
+ if (qe++ < 0 || qe == value.length()) return __one;
+
+ if (value.charAt(qe++) == 'q')
+ {
+ qe++;
+ Float q = __qualities.get(value, qe, value.length() - qe);
+ if (q != null)
+ return q;
+ }
+
+ Map params = new HashMap<>(4);
+ valueParameters(value, params);
+ String qs = params.get("q");
+ if (qs==null)
+ qs="*";
+ Float q = __qualities.get(qs);
+ if (q == null)
+ {
+ try
+ {
+ q = new Float(qs);
+ }
+ catch (Exception e)
+ {
+ q = __one;
+ }
+ }
+ return q;
+ }
+
+ /**
+ * List values in quality order.
+ *
+ * @param e Enumeration of values with quality parameters
+ * @return values in quality order.
+ */
+ public static List qualityList(Enumeration e)
+ {
+ if (e == null || !e.hasMoreElements())
+ return Collections.emptyList();
+
+ Object list = null;
+ Object qual = null;
+
+ // Assume list will be well ordered and just add nonzero
+ while (e.hasMoreElements())
+ {
+ String v = e.nextElement();
+ Float q = getQuality(v);
+
+ if (q >= 0.001)
+ {
+ list = LazyList.add(list, v);
+ qual = LazyList.add(qual, q);
+ }
+ }
+
+ List vl = LazyList.getList(list, false);
+ if (vl.size() < 2)
+ return vl;
+
+ List ql = LazyList.getList(qual, false);
+
+ // sort list with swaps
+ Float last = __zero;
+ for (int i = vl.size(); i-- > 0;)
+ {
+ Float q = ql.get(i);
+ if (last.compareTo(q) > 0)
+ {
+ String tmp = vl.get(i);
+ vl.set(i, vl.get(i + 1));
+ vl.set(i + 1, tmp);
+ ql.set(i, ql.get(i + 1));
+ ql.set(i + 1, q);
+ last = __zero;
+ i = vl.size();
+ continue;
+ }
+ last = q;
+ }
+ ql.clear();
+ return vl;
+ }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644
index 00000000..a51e4ba7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
@@ -0,0 +1,1104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ *
+ */
+public class HttpGenerator
+{
+ private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
+
+ private final static byte[] __colon_space = new byte[] {':',' '};
+ private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+ public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+ public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+ public final static ResponseInfo RESPONSE_500_INFO =
+ new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
+
+ // states
+ public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+ public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+ // other statics
+ public static final int CHUNK_SIZE = 12;
+
+ private State _state = State.START;
+ private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+ private long _contentPrepared = 0;
+ private boolean _noContent = false;
+ private Boolean _persistent = null;
+
+ private final int _send;
+ private final static int SEND_SERVER = 0x01;
+ private final static int SEND_XPOWEREDBY = 0x02;
+
+
+ /* ------------------------------------------------------------------------------- */
+ public static void setJettyVersion(String serverVersion)
+ {
+ SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+ SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+ SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+ serverVersion + "\015\012");
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ // data
+ private boolean _needCRLF = false;
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator()
+ {
+ this(false,false);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+ {
+ _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ _state = State.START;
+ _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+ _noContent=false;
+ _persistent = null;
+ _contentPrepared = 0;
+ _needCRLF = false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public boolean getSendServerVersion ()
+ {
+ return (_send&SEND_SERVER)!=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isState(State state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return _state == State.START;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isEnd()
+ {
+ return _state == State.END;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCommitted()
+ {
+ return _state.ordinal() >= State.COMMITTED.ordinal();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isChunking()
+ {
+ return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPersistent(boolean persistent)
+ {
+ _persistent=persistent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if known to be persistent
+ */
+ public boolean isPersistent()
+ {
+ return Boolean.TRUE.equals(_persistent);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isWritten()
+ {
+ return _contentPrepared>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentPrepared()
+ {
+ return _contentPrepared;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void abort()
+ {
+ _persistent=false;
+ _state=State.END;
+ _endOfContent=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+
+ // Do we need a request header
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // If we have not been told our persistence, set the default
+ if (_persistent==null)
+ _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateRequestLine(info,header);
+
+ if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+ _noContent=true;
+ else
+ generateHeaders(info,header,content,last);
+
+ boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+ if (expect100)
+ {
+ _state = State.COMMITTED;
+ }
+ else
+ {
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+
+ return Result.FLUSH;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+ throw new IOException(message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ if (len>0)
+ {
+ // Do we need a chunk buffer?
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ {
+ _state=State.COMPLETING;
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+
+ return Result.FLUSH;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+
+ // Handle 0.9
+ if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+ {
+ _persistent = false;
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ if (BufferUtil.hasContent(content))
+ _contentPrepared+=content.remaining();
+ _state = last?State.COMPLETING:State.COMMITTED;
+ return Result.FLUSH;
+ }
+
+ // Do we need a response header
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // If we have not been told our persistence, set the default
+ if (_persistent==null)
+ _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateResponseLine(info,header);
+
+ // Handle 1xx and no content responses
+ int status=info.getStatus();
+ if (status>=100 && status<200 )
+ {
+ _noContent=true;
+
+ if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+ {
+ header.put(HttpTokens.CRLF);
+ _state=State.COMPLETING_1XX;
+ return Result.FLUSH;
+ }
+ }
+ else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+ {
+ _noContent=true;
+ }
+
+ generateHeaders(info,header,content,last);
+
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking() && !info.isHead())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+ throw new IOException(message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+
+ return Result.FLUSH;
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ // handle the content.
+ if (len>0)
+ {
+ if (isChunking())
+ {
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ {
+ _state=State.COMPLETING;
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+ return len>0?Result.FLUSH:Result.DONE;
+
+ }
+
+ case COMPLETING_1XX:
+ {
+ reset();
+ return Result.DONE;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+
+ // Write the last chunk
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void prepareChunk(ByteBuffer chunk, int remaining)
+ {
+ // if we need CRLF add this to header
+ if (_needCRLF)
+ BufferUtil.putCRLF(chunk);
+
+ // Add the chunk size to the header
+ if (remaining>0)
+ {
+ BufferUtil.putHexInt(chunk, remaining);
+ BufferUtil.putCRLF(chunk);
+ _needCRLF=true;
+ }
+ else
+ {
+ chunk.put(LAST_CHUNK);
+ _needCRLF=false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateRequestLine(RequestInfo request,ByteBuffer header)
+ {
+ header.put(StringUtil.getBytes(request.getMethod()));
+ header.put((byte)' ');
+ header.put(StringUtil.getBytes(request.getUri()));
+ switch(request.getHttpVersion())
+ {
+ case HTTP_1_0:
+ case HTTP_1_1:
+ header.put((byte)' ');
+ header.put(request.getHttpVersion().toBytes());
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+ {
+ // Look for prepared response line
+ int status=response.getStatus();
+ PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+ String reason=response.getReason();
+ if (preprepared!=null)
+ {
+ if (reason==null)
+ header.put(preprepared._responseLine);
+ else
+ {
+ header.put(preprepared._schemeCode);
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else // generate response line
+ {
+ header.put(HTTP_1_1_SPACE);
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ header.put((byte) ' ');
+ if (reason==null)
+ {
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ }
+ else
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private byte[] getReasonBytes(String reason)
+ {
+ if (reason.length()>1024)
+ reason=reason.substring(0,1024);
+ byte[] _bytes = StringUtil.getBytes(reason);
+
+ for (int i=_bytes.length;i-->0;)
+ if (_bytes[i]=='\r' || _bytes[i]=='\n')
+ _bytes[i]='?';
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+ {
+ final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+ final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+ // default field values
+ int send=_send;
+ HttpField transfer_encoding=null;
+ boolean keep_alive=false;
+ boolean close=false;
+ boolean content_type=false;
+ StringBuilder connection = null;
+
+ // Generate fields
+ if (_info.getHttpFields() != null)
+ {
+ for (HttpField field : _info.getHttpFields())
+ {
+ HttpHeader h = field.getHeader();
+
+ switch (h==null?HttpHeader.UNKNOWN:h)
+ {
+ case CONTENT_LENGTH:
+ // handle specially below
+ if (_info.getContentLength()>=0)
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ break;
+
+ case CONTENT_TYPE:
+ {
+ if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+ _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
+
+ // write the field to the header
+ content_type=true;
+ putTo(field,header);
+ break;
+ }
+
+ case TRANSFER_ENCODING:
+ {
+ if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+ transfer_encoding = field;
+ // Do NOT add yet!
+ break;
+ }
+
+ case CONNECTION:
+ {
+ if (request!=null)
+ putTo(field,header);
+
+ // Lookup and/or split connection value field
+ HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+ String[] split = null;
+
+ if (values[0]==null)
+ {
+ split = field.getValue().split("\\s*,\\s*");
+ if (split.length>0)
+ {
+ values=new HttpHeaderValue[split.length];
+ for (int i=0;i0)
+ {
+ // we have been given a content length
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ long content_length = _info.getContentLength();
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ // known length but not actually set.
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else if (last)
+ {
+ // we have seen all the _content there is, so we can be content-length limited.
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ long content_length = _contentPrepared+BufferUtil.length(content);
+
+ // Do we need to tell the headers about it
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else
+ {
+ // No idea, so we must assume that a body is coming.
+ _endOfContent = EndOfContent.CHUNKED_CONTENT;
+ // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+ // For a request with HTTP 1.0 & Connection: keep-alive
+ // we *must* close the connection, otherwise the client
+ // has no way to detect the end of the content.
+ if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+ _endOfContent = EndOfContent.EOF_CONTENT;
+ }
+ break;
+
+ case CONTENT_LENGTH:
+ long content_length = _info.getContentLength();
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ // known length but not actually set.
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ break;
+
+ case NO_CONTENT:
+ if (response!=null && status >= 200 && status != 204 && status != 304)
+ header.put(CONTENT_LENGTH_0);
+ break;
+
+ case EOF_CONTENT:
+ _persistent = request!=null;
+ break;
+
+ case CHUNKED_CONTENT:
+ break;
+
+ default:
+ break;
+ }
+
+ // Add transfer_encoding if needed
+ if (isChunking())
+ {
+ // try to use user supplied encoding as it may have other values.
+ if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+ {
+ String c = transfer_encoding.getValue();
+ if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ putTo(transfer_encoding,header);
+ else
+ throw new IllegalArgumentException("BAD TE");
+ }
+ else
+ header.put(TRANSFER_ENCODING_CHUNKED);
+ }
+
+ // Handle connection if need be
+ if (_endOfContent==EndOfContent.EOF_CONTENT)
+ {
+ keep_alive=false;
+ _persistent=false;
+ }
+
+ // If this is a response, work out persistence
+ if (response!=null)
+ {
+ if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+ {
+ if (connection==null)
+ header.put(CONNECTION_CLOSE);
+ else
+ {
+ header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (keep_alive)
+ {
+ if (connection==null)
+ header.put(CONNECTION_KEEP_ALIVE);
+ else
+ {
+ header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (connection!=null)
+ {
+ header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+
+ if (status>199)
+ header.put(SEND[send]);
+
+ // end the header.
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public static byte[] getReasonBuffer(int code)
+ {
+ PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+ if (status!=null)
+ return status._reason;
+ return null;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s}",
+ getClass().getSimpleName(),
+ _state);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // common _content
+ private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+ private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+ private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+ private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+ private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+ private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+ private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+ private static final byte[][] SEND = new byte[][]{
+ new byte[0],
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+ };
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // Build cache of response lines for status
+ private static class PreparedResponse
+ {
+ byte[] _reason;
+ byte[] _schemeCode;
+ byte[] _responseLine;
+ }
+ private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+ static
+ {
+ int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+ for (int i=0;i<__preprepared.length;i++)
+ {
+ HttpStatus.Code code = HttpStatus.getCode(i);
+ if (code==null)
+ continue;
+ String reason=code.getMessage();
+ byte[] line=new byte[versionLength+5+reason.length()+2];
+ HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+ line[versionLength+0]=' ';
+ line[versionLength+1]=(byte)('0'+i/100);
+ line[versionLength+2]=(byte)('0'+(i%100)/10);
+ line[versionLength+3]=(byte)('0'+(i%10));
+ line[versionLength+4]=' ';
+ for (int j=0;j=100 && _status<200;
+ }
+
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ public boolean isHead()
+ {
+ return _head;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
+ }
+ }
+
+ private static void putSanitisedName(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i0xff || c=='\r' || c=='\n'|| c==':')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ private static void putSanitisedValue(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i0xff || c=='\r' || c=='\n')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+ {
+ if (field instanceof CachedHttpField)
+ {
+ ((CachedHttpField)field).putTo(bufferInFillMode);
+ }
+ else
+ {
+ HttpHeader header=field.getHeader();
+ if (header!=null)
+ {
+ bufferInFillMode.put(header.getBytesColonSpace());
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+ else
+ {
+ putSanitisedName(field.getName(),bufferInFillMode);
+ bufferInFillMode.put(__colon_space);
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+ }
+
+ public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
+ {
+ for (HttpField field : fields)
+ {
+ if (field != null)
+ putTo(field,bufferInFillMode);
+ }
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+
+ public static class CachedHttpField extends HttpField
+ {
+ private final byte[] _bytes;
+ public CachedHttpField(HttpHeader header,String value)
+ {
+ super(header,value);
+ int cbl=header.getBytesColonSpace().length;
+ _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+ System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+ _bytes[_bytes.length-2]=(byte)'\r';
+ _bytes[_bytes.length-1]=(byte)'\n';
+ }
+
+ public void putTo(ByteBuffer bufferInFillMode)
+ {
+ bufferInFillMode.put(_bytes);
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeader.java b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644
index 00000000..ab0ddcf3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
@@ -0,0 +1,178 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+ /* ------------------------------------------------------------ */
+ /** General Fields.
+ */
+ CONNECTION("Connection"),
+ CACHE_CONTROL("Cache-Control"),
+ DATE("Date"),
+ PRAGMA("Pragma"),
+ PROXY_CONNECTION ("Proxy-Connection"),
+ TRAILER("Trailer"),
+ TRANSFER_ENCODING("Transfer-Encoding"),
+ UPGRADE("Upgrade"),
+ VIA("Via"),
+ WARNING("Warning"),
+ NEGOTIATE("Negotiate"),
+
+ /* ------------------------------------------------------------ */
+ /** Entity Fields.
+ */
+ ALLOW("Allow"),
+ CONTENT_ENCODING("Content-Encoding"),
+ CONTENT_LANGUAGE("Content-Language"),
+ CONTENT_LENGTH("Content-Length"),
+ CONTENT_LOCATION("Content-Location"),
+ CONTENT_MD5("Content-MD5"),
+ CONTENT_RANGE("Content-Range"),
+ CONTENT_TYPE("Content-Type"),
+ EXPIRES("Expires"),
+ LAST_MODIFIED("Last-Modified"),
+
+ /* ------------------------------------------------------------ */
+ /** Request Fields.
+ */
+ ACCEPT("Accept"),
+ ACCEPT_CHARSET("Accept-Charset"),
+ ACCEPT_ENCODING("Accept-Encoding"),
+ ACCEPT_LANGUAGE("Accept-Language"),
+ AUTHORIZATION("Authorization"),
+ EXPECT("Expect"),
+ FORWARDED("Forwarded"),
+ FROM("From"),
+ HOST("Host"),
+ IF_MATCH("If-Match"),
+ IF_MODIFIED_SINCE("If-Modified-Since"),
+ IF_NONE_MATCH("If-None-Match"),
+ IF_RANGE("If-Range"),
+ IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+ KEEP_ALIVE("Keep-Alive"),
+ MAX_FORWARDS("Max-Forwards"),
+ PROXY_AUTHORIZATION("Proxy-Authorization"),
+ RANGE("Range"),
+ REQUEST_RANGE("Request-Range"),
+ REFERER("Referer"),
+ TE("TE"),
+ USER_AGENT("User-Agent"),
+ X_FORWARDED_FOR("X-Forwarded-For"),
+ X_FORWARDED_PROTO("X-Forwarded-Proto"),
+ X_FORWARDED_SERVER("X-Forwarded-Server"),
+ X_FORWARDED_HOST("X-Forwarded-Host"),
+
+ /* ------------------------------------------------------------ */
+ /** Response Fields.
+ */
+ ACCEPT_RANGES("Accept-Ranges"),
+ AGE("Age"),
+ ETAG("ETag"),
+ LOCATION("Location"),
+ PROXY_AUTHENTICATE("Proxy-Authenticate"),
+ RETRY_AFTER("Retry-After"),
+ SERVER("Server"),
+ SERVLET_ENGINE("Servlet-Engine"),
+ VARY("Vary"),
+ WWW_AUTHENTICATE("WWW-Authenticate"),
+
+ /* ------------------------------------------------------------ */
+ /** Other Fields.
+ */
+ COOKIE("Cookie"),
+ SET_COOKIE("Set-Cookie"),
+ SET_COOKIE2("Set-Cookie2"),
+ MIME_VERSION("MIME-Version"),
+ IDENTITY("identity"),
+
+ X_POWERED_BY("X-Powered-By"),
+
+ UNKNOWN("::UNKNOWN::");
+
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie<>(512);
+ static
+ {
+ for (HttpHeader header : HttpHeader.values())
+ if (header!=UNKNOWN)
+ CACHE.put(header.toString(),header);
+ }
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final byte[] _bytesColonSpace;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeader(String s)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _bytesColonSpace=StringUtil.getBytes(s+": ");
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytesColonSpace()
+ {
+ return _bytesColonSpace;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644
index 00000000..8338a322
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ *
+ */
+public enum HttpHeaderValue
+{
+ CLOSE("close"),
+ CHUNKED("chunked"),
+ GZIP("gzip"),
+ IDENTITY("identity"),
+ KEEP_ALIVE("keep-alive"),
+ CONTINUE("100-continue"),
+ PROCESSING("102-processing"),
+ TE("TE"),
+ BYTES("bytes"),
+ NO_CACHE("no-cache"),
+ UPGRADE("Upgrade"),
+ UNKNOWN("::UNKNOWN::");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpHeaderValue value : HttpHeaderValue.values())
+ if (value!=UNKNOWN)
+ CACHE.put(value.toString(),value);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeaderValue(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ private static EnumSet __known =
+ EnumSet.of(HttpHeader.CONNECTION,
+ HttpHeader.TRANSFER_ENCODING,
+ HttpHeader.CONTENT_ENCODING);
+
+ /* ------------------------------------------------------------ */
+ public static boolean hasKnownValues(HttpHeader header)
+ {
+ if (header==null)
+ return false;
+ return __known.contains(header);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpMethod.java b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644
index 00000000..8a262680
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
@@ -0,0 +1,174 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+ GET,
+ POST,
+ HEAD,
+ PUT,
+ OPTIONS,
+ DELETE,
+ TRACE,
+ CONNECT,
+ MOVE,
+ PROXY;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a method name and trailing space in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+ {
+ int length=limit-position;
+ if (length<4)
+ return null;
+ switch(bytes[position])
+ {
+ case 'G':
+ if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return GET;
+ break;
+ case 'P':
+ if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+ return POST;
+ if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+ return PROXY;
+ if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return PUT;
+ break;
+ case 'H':
+ if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+ return HEAD;
+ break;
+ case 'O':
+ if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+ bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+ return OPTIONS;
+ break;
+ case 'D':
+ if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+ bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+ return DELETE;
+ break;
+ case 'T':
+ if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+ bytes[position+4]=='E' && bytes[position+5]==' ' )
+ return TRACE;
+ break;
+ case 'C':
+ if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+ bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+ return CONNECT;
+ break;
+ case 'M':
+ if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && bytes[position+4]==' ')
+ return MOVE;
+ break;
+
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a method name and trailing space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+
+ // TODO use cache and check for space
+ // return CACHE.getBest(buffer,0,buffer.remaining());
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie<>();
+ static
+ {
+ for (HttpMethod method : HttpMethod.values())
+ CACHE.put(method.toString(),method);
+ }
+
+ /* ------------------------------------------------------------ */
+ private final ByteBuffer _buffer;
+ private final byte[] _bytes;
+
+ /* ------------------------------------------------------------ */
+ HttpMethod()
+ {
+ _bytes=StringUtil.getBytes(toString());
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return toString().equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return toString();
+ }
+
+ /**
+ * Converts the given String parameter to an HttpMethod
+ * @param method the String to get the equivalent HttpMethod from
+ * @return the HttpMethod or null if the parameter method is unknown
+ */
+ public static HttpMethod fromString(String method)
+ {
+ return CACHE.get(method);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpParser.java b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
new file mode 100644
index 00000000..79f1c28d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
@@ -0,0 +1,1688 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ *
+ * The is parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method. The parsed
+ * elements of the HTTP message are passed as event calls to the
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then
+ * client side parsing is done.
+ *
+ *
+ * The contract of the {@link HttpHandler} API is that if a call returns
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as
+ * soon as possible also with a true response. Typically this indicates
+ * that the parsing has reached a stage where the caller should process
+ * the events accumulated by the handler. It is the preferred calling
+ * style that handling such as calling a servlet to process a request,
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like
+ * {@link RequestHandler#messageComplete()}
+ *
+ *
+ * For performance, the parse is heavily dependent on the
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ *
+ *
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ *
+ */
+public class HttpParser
+{
+ public static final Logger LOG = Log.getLogger(HttpParser.class);
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
+ public final static int INITIAL_URI_LENGTH=256;
+
+ /**
+ * Cache of common {@link HttpField}s including:
+ * Common static combinations such as:
+ * Connection: close
+ * Accept-Encoding: gzip
+ * Content-Length: 0
+ *
+ * Combinations of Content-Type header for common mime types by common charsets
+ * Most common headers with null values so that a lookup will at least
+ * determine the header name even if the name:value combination is not cached
+ *
+ */
+ public final static Trie CACHE = new ArrayTrie<>(2048);
+
+ // States
+ public enum State
+ {
+ START,
+ METHOD,
+ RESPONSE_VERSION,
+ SPACE1,
+ STATUS,
+ URI,
+ SPACE2,
+ REQUEST_VERSION,
+ REASON,
+ PROXY,
+ HEADER,
+ HEADER_IN_NAME,
+ HEADER_VALUE,
+ HEADER_IN_VALUE,
+ CONTENT,
+ EOF_CONTENT,
+ CHUNKED_CONTENT,
+ CHUNK_SIZE,
+ CHUNK_PARAMS,
+ CHUNK,
+ END,
+ CLOSED
+ }
+
+ private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+ private final HttpHandler _handler;
+ private final RequestHandler _requestHandler;
+ private final ResponseHandler _responseHandler;
+ private final int _maxHeaderBytes;
+ private final boolean _strict;
+ private HttpField _field;
+ private HttpHeader _header;
+ private String _headerString;
+ private HttpHeaderValue _value;
+ private String _valueString;
+ private int _responseStatus;
+ private int _headerBytes;
+ private boolean _host;
+
+ /* ------------------------------------------------------------------------------- */
+ private volatile State _state=State.START;
+ private volatile boolean _eof;
+ private volatile boolean _closed;
+ private HttpMethod _method;
+ private String _methodString;
+ private HttpVersion _version;
+ private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+ private EndOfContent _endOfContent;
+ private long _contentLength;
+ private long _contentPosition;
+ private int _chunkLength;
+ private int _chunkPosition;
+ private boolean _headResponse;
+ private boolean _cr;
+ private ByteBuffer _contentChunk;
+ private Trie _connectionFields;
+
+ private int _length;
+ private final StringBuilder _string=new StringBuilder();
+
+ static
+ {
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+ CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+ CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+
+ // Add common Content types as fields
+ for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+ {
+ HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+ CACHE.put(field);
+
+ for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+ {
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+ }
+ }
+
+ // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+ for (HttpHeader h:HttpHeader.values())
+ if (!CACHE.put(new HttpField(h,(String)null)))
+ throw new IllegalStateException("CACHE FULL");
+ // Add some more common headers
+ CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+ CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+ CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=null;
+ _responseHandler=handler;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public long getContentLength()
+ {
+ return _contentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _contentPosition;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set if a HEAD response is expected
+ * @param head
+ */
+ public void setHeadResponse(boolean head)
+ {
+ _headResponse=head;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setResponseStatus(int status)
+ {
+ _responseStatus=status;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean inContentState()
+ {
+ return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()=0 && ch0 && _state.ordinal() HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+ return false;
+ }
+ else if (ch==0)
+ break;
+ else if (ch<0)
+ throw new BadMessage();
+
+ // count this white space as a header byte to avoid DOS
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("padding is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400);
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private void setString(String s)
+ {
+ _string.setLength(0);
+ _string.append(s);
+ _length=s.length();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private String takeString()
+ {
+ _string.setLength(_length);
+ String s =_string.toString();
+ _string.setLength(0);
+ _length=-1;
+ return s;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Parse a request or response line
+ */
+ private boolean parseLine(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ if (_state==State.URI)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ else
+ {
+ if (_requestHandler!=null)
+ LOG.warn("request is too large >"+_maxHeaderBytes);
+ else
+ LOG.warn("response is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+ }
+
+ switch (_state)
+ {
+ case METHOD:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ _methodString=takeString();
+ HttpMethod method=HttpMethod.CACHE.get(_methodString);
+ if (method!=null && !_strict)
+ _methodString=method.asString();
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No URI");
+ else
+ _string.append((char)ch);
+ break;
+
+ case RESPONSE_VERSION:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ String version=takeString();
+ _version=HttpVersion.CACHE.get(version);
+ if (_version==null)
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No Status");
+ else
+ _string.append((char)ch);
+ break;
+
+ case SPACE1:
+ if (ch > HttpTokens.SPACE || ch<0)
+ {
+ if (_responseHandler!=null)
+ {
+ setState(State.STATUS);
+ setResponseStatus(ch-'0');
+ }
+ else
+ {
+ _uri.clear();
+ setState(State.URI);
+ // quick scan for space or EoBuffer
+ if (buffer.hasArray())
+ {
+ byte[] array=buffer.array();
+ int p=buffer.arrayOffset()+buffer.position();
+ int l=buffer.arrayOffset()+buffer.limit();
+ int i=p;
+ while (iHttpTokens.SPACE)
+ i++;
+
+ int len=i-p;
+ _headerBytes+=len;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ if (_uri.remaining()<=len)
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(array,p-1,len+1);
+ buffer.position(i-buffer.arrayOffset());
+ }
+ else
+ _uri.put(ch);
+ }
+ }
+ else if (ch < HttpTokens.SPACE)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ }
+ break;
+
+ case STATUS:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch>='0' && ch<='9')
+ {
+ _responseStatus=_responseStatus*10+(ch-'0');
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ setState(State.HEADER);
+ }
+ else
+ {
+ throw new BadMessage();
+ }
+ break;
+
+ case URI:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ // HTTP/0.9
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ }
+ else
+ {
+ if (!_uri.hasRemaining())
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(ch);
+ }
+ break;
+
+ case SPACE2:
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ if (_responseHandler!=null)
+ {
+ _length=1;
+ setState(State.REASON);
+ }
+ else
+ {
+ setState(State.REQUEST_VERSION);
+
+ // try quick look ahead for HTTP Version
+ HttpVersion version;
+ if (buffer.position()>0 && buffer.hasArray())
+ version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ else
+ version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+ if (version==null)
+ {
+ if (_method==HttpMethod.PROXY)
+ {
+ if (!(_requestHandler instanceof ProxyHandler))
+ throw new BadMessage();
+
+ _uri.flip();
+ String protocol=BufferUtil.toString(_uri);
+ // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+ buffer.position(buffer.position()-1);
+ String sAddr = getProxyField(buffer);
+ String dAddr = getProxyField(buffer);
+ int sPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ int dPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ _state=State.START;
+ ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+ return false;
+ }
+ }
+ else
+ {
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos=HttpVersion.HTTP_1_1.getVersion())
+ {
+ int header_cache = _handler.getHeaderCacheSize();
+ _connectionFields=new ArrayTernaryTrie<>(header_cache);
+ }
+
+ setState(State.HEADER);
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ _string.append((char)ch);
+ else
+ throw new BadMessage();
+
+ break;
+
+ case REASON:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ String reason=takeString();
+
+ setState(State.HEADER);
+ handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ {
+ _string.append((char)ch);
+ if (ch!=' '&&ch!='\t')
+ _length=_string.length();
+ }
+ else
+ throw new BadMessage();
+ break;
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ private boolean handleKnownHeaders(ByteBuffer buffer)
+ {
+ boolean add_to_connection_trie=false;
+ switch (_header)
+ {
+ case CONTENT_LENGTH:
+ if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+ {
+ try
+ {
+ _contentLength=Long.parseLong(_valueString);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.ignore(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+ }
+ if (_contentLength <= 0)
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ }
+ break;
+
+ case TRANSFER_ENCODING:
+ if (_value==HttpHeaderValue.CHUNKED)
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else
+ {
+ if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+ }
+ }
+ break;
+
+ case HOST:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ _host=true;
+ String host=_valueString;
+ int port=0;
+ if (host==null || host.length()==0)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+
+ int len=host.length();
+ loop: for (int i = len; i-- > 0;)
+ {
+ char c2 = (char)(0xff & host.charAt(i));
+ switch (c2)
+ {
+ case ']':
+ break loop;
+
+ case ':':
+ try
+ {
+ len=i;
+ port = StringUtil.toInt(host.substring(i+1));
+ }
+ catch (NumberFormatException e)
+ {
+ if (DEBUG)
+ LOG.debug(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+ break loop;
+ }
+ }
+ if (host.charAt(0)=='[')
+ {
+ if (host.charAt(len-1)!=']')
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+ }
+ host = host.substring(1,len-1);
+ }
+ else if (len!=host.length())
+ host = host.substring(0,len);
+
+ if (_requestHandler!=null)
+ _requestHandler.parsedHostHeader(host,port);
+
+ break;
+
+ case CONNECTION:
+ // Don't cache if not persistent
+ if (_valueString!=null && _valueString.contains("close"))
+ {
+ _closed=true;
+ _connectionFields=null;
+ }
+ break;
+
+ case AUTHORIZATION:
+ case ACCEPT:
+ case ACCEPT_CHARSET:
+ case ACCEPT_ENCODING:
+ case ACCEPT_LANGUAGE:
+ case COOKIE:
+ case CACHE_CONTROL:
+ case USER_AGENT:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ break;
+
+ default: break;
+ }
+
+ if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+ {
+ _field=new HttpField(_header,_valueString);
+ _connectionFields.put(_field);
+ }
+
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * Parse the message headers and return true if the handler has signaled for a return
+ */
+ protected boolean parseHeaders(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("Header is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+
+ switch (_state)
+ {
+ case HEADER:
+ switch(ch)
+ {
+ case HttpTokens.COLON:
+ case HttpTokens.SPACE:
+ case HttpTokens.TAB:
+ {
+ // header value without name - continuation?
+ if (_valueString==null)
+ {
+ _string.setLength(0);
+ _length=0;
+ }
+ else
+ {
+ setString(_valueString);
+ _string.append(' ');
+ _length++;
+ _valueString=null;
+ }
+ setState(State.HEADER_VALUE);
+ break;
+ }
+
+ default:
+ {
+ // handler last header if any. Delayed to here just in case there was a continuation line (above)
+ if (_headerString!=null || _valueString!=null)
+ {
+ // Handle known headers
+ if (_header!=null && handleKnownHeaders(buffer))
+ {
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+ return true;
+ }
+ handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+ }
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+
+ // now handle the ch
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ _contentPosition=0;
+
+ // End of headers!
+
+ // Was there a required host header?
+ if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+ }
+
+ // is it a response that cannot have a body?
+ if (_responseHandler !=null && // response
+ (_responseStatus == 304 || // not-modified response
+ _responseStatus == 204 || // no-content response
+ _responseStatus < 200)) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+
+ // else if we don't know framing
+ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+ {
+ if (_responseStatus == 0 // request
+ || _responseStatus == 304 // not-modified response
+ || _responseStatus == 204 // no-content response
+ || _responseStatus < 200) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ }
+
+ // How is the message ended?
+ switch (_endOfContent)
+ {
+ case EOF_CONTENT:
+ setState(State.EOF_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case CHUNKED_CONTENT:
+ setState(State.CHUNKED_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case NO_CONTENT:
+ handle=_handler.headerComplete()||handle;
+ setState(State.END);
+ handle=_handler.messageComplete()||handle;
+ break;
+
+ default:
+ setState(State.CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+ }
+ }
+ else if (ch<=HttpTokens.SPACE)
+ throw new BadMessage();
+ else
+ {
+ if (buffer.hasRemaining())
+ {
+ // Try a look ahead for the known header name and value.
+ HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+ if (field==null)
+ field=CACHE.getBest(buffer,-1,buffer.remaining());
+
+ if (field!=null)
+ {
+ final String n;
+ final String v;
+
+ if (_strict)
+ {
+ // Have to get the fields exactly from the buffer to match case
+ String fn=field.getName();
+ String fv=field.getValue();
+ n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+ if (fv==null)
+ v=null;
+ else
+ {
+ v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+ field=new HttpField(field.getHeader(),n,v);
+ }
+ }
+ else
+ {
+ n=field.getName();
+ v=field.getValue();
+ }
+
+ _header=field.getHeader();
+ _headerString=n;
+
+ if (v==null)
+ {
+ // Header only
+ setState(State.HEADER_VALUE);
+ _string.setLength(0);
+ _length=0;
+ buffer.position(buffer.position()+n.length()+1);
+ break;
+ }
+ else
+ {
+ // Header and value
+ int pos=buffer.position()+n.length()+v.length()+1;
+ byte b=buffer.get(pos);
+
+ if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+ {
+ _field=field;
+ _valueString=v;
+ setState(State.HEADER_IN_VALUE);
+
+ if (b==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ buffer.position(pos+1);
+ }
+ else
+ buffer.position(pos);
+ break;
+ }
+ else
+ {
+ setState(State.HEADER_IN_VALUE);
+ setString(v);
+ buffer.position(pos);
+ break;
+ }
+ }
+ }
+ }
+
+ // New header
+ setState(State.HEADER_IN_NAME);
+ _string.setLength(0);
+ _string.append((char)ch);
+ _length=1;
+ }
+ }
+ }
+ break;
+
+ case HEADER_IN_NAME:
+ if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
+ {
+ if (_headerString==null)
+ {
+ _headerString=takeString();
+ _header=HttpHeader.CACHE.get(_headerString);
+ }
+ _length=-1;
+
+ setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+ break;
+ }
+
+ if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+ {
+ if (_header!=null)
+ {
+ setString(_header.asString());
+ _header=null;
+ _headerString=null;
+ }
+
+ _string.append((char)ch);
+ if (ch>HttpTokens.SPACE)
+ _length=_string.length();
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_VALUE:
+ if (ch>HttpTokens.SPACE || ch<0)
+ {
+ _string.append((char)(0xff&ch));
+ _length=_string.length();
+ setState(State.HEADER_IN_VALUE);
+ break;
+ }
+
+ if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+ break;
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+ }
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_IN_VALUE:
+ if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+ {
+ if (_valueString!=null)
+ {
+ setString(_valueString);
+ _valueString=null;
+ _field=null;
+ }
+ _string.append((char)(0xff&ch));
+ if (ch>HttpTokens.SPACE || ch<0)
+ _length=_string.length();
+ break;
+ }
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=takeString();
+ _length=-1;
+ }
+ setState(State.HEADER);
+ break;
+ }
+ throw new BadMessage("Illegal character");
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until next Event.
+ * @return True if an {@link RequestHandler} method was called and it returned true;
+ */
+ public boolean parseNext(ByteBuffer buffer)
+ {
+ if (DEBUG)
+ LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+ try
+ {
+ // Start a request/response
+ if (_state==State.START)
+ {
+ _version=null;
+ _method=null;
+ _methodString=null;
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _header=null;
+ if (quickStart(buffer))
+ return true;
+ }
+
+ // Request/response line
+ if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()0 && _headResponse)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ if (parseContent(buffer))
+ return true;
+ }
+ }
+
+ // handle end states
+ if (_state==State.END)
+ {
+ // eat white space
+ while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+ buffer.get();
+ }
+ else if (_state==State.CLOSED)
+ {
+ if (BufferUtil.hasContent(buffer))
+ {
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
+ if (_headerBytes>_maxHeaderBytes)
+ {
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data after closed");
+ }
+ }
+ }
+
+ // Handle EOF
+ if (_eof && !buffer.hasRemaining())
+ {
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+
+ case START:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ case END:
+ setState(State.CLOSED);
+ break;
+
+ case EOF_CONTENT:
+ setState(State.CLOSED);
+ return _handler.messageComplete();
+
+ case CONTENT:
+ case CHUNKED_CONTENT:
+ case CHUNK_SIZE:
+ case CHUNK_PARAMS:
+ case CHUNK:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} EOF in {}",this,_state);
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ break;
+ }
+ }
+
+ return false;
+ }
+ catch(BadMessage e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+ setState(State.CLOSED);
+ _handler.badMessage(e._code, e._message);
+ return false;
+ }
+ catch(Exception e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+
+ if (_state.ordinal()<=State.END.ordinal())
+ {
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ }
+ else
+ {
+ _handler.earlyEOF();
+ setState(State.CLOSED);
+ }
+
+ return false;
+ }
+ }
+
+ protected boolean parseContent(ByteBuffer buffer)
+ {
+ int remaining=buffer.remaining();
+ if (remaining==0 && _state==State.CONTENT)
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+
+ // Handle _content
+ byte ch;
+ while (_state.ordinal() < State.END.ordinal() && remaining>0)
+ {
+ switch (_state)
+ {
+ case EOF_CONTENT:
+ _contentChunk=buffer.asReadOnlyBuffer();
+ _contentPosition += remaining;
+ buffer.position(buffer.position()+remaining);
+ if (_handler.content(_contentChunk))
+ return true;
+ break;
+
+ case CONTENT:
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ // limit content by expected size
+ if (remaining > content)
+ {
+ // We can cast remaining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ _contentChunk.limit(_contentChunk.position()+(int)content);
+ }
+
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+
+ if (_handler.content(_contentChunk))
+ return true;
+
+ if(_contentPosition == _contentLength)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+ break;
+ }
+
+ case CHUNKED_CONTENT:
+ {
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
+ {
+ _chunkLength=TypeUtil.convertHexDigit(ch);
+ _chunkPosition=0;
+ setState(State.CHUNK_SIZE);
+ }
+
+ break;
+ }
+
+ case CHUNK_SIZE:
+ {
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+ setState(State.CHUNK_PARAMS);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+ break;
+ }
+
+ case CHUNK_PARAMS:
+ {
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ break;
+ }
+
+ case CHUNK:
+ {
+ int chunk=_chunkLength - _chunkPosition;
+ if (chunk == 0)
+ {
+ setState(State.CHUNKED_CONTENT);
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ if (remaining > chunk)
+ _contentChunk.limit(_contentChunk.position()+chunk);
+ chunk=_contentChunk.remaining();
+
+ _contentPosition += chunk;
+ _chunkPosition += chunk;
+ buffer.position(buffer.position()+chunk);
+ if (_handler.content(_contentChunk))
+ return true;
+ }
+ break;
+ }
+
+ case CLOSED:
+ {
+ BufferUtil.clear(buffer);
+ return false;
+ }
+
+ default:
+ break;
+
+ }
+
+ remaining=buffer.remaining();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isAtEOF()
+
+ {
+ return _eof;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void atEOF()
+
+ {
+ if (DEBUG)
+ LOG.debug("atEOF {}", this);
+ _eof=true;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void close()
+ {
+ if (DEBUG)
+ LOG.debug("close {}", this);
+ setState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ if (DEBUG)
+ LOG.debug("reset {}", this);
+ // reset state
+ if (_state==State.CLOSED)
+ return;
+ if (_closed)
+ {
+ setState(State.CLOSED);
+ return;
+ }
+
+ setState(State.START);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _contentLength=-1;
+ _contentPosition=0;
+ _responseStatus=0;
+ _contentChunk=null;
+ _headerBytes=0;
+ _host=false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setState(State state)
+ {
+ if (DEBUG)
+ LOG.debug("{} --> {}",_state,state);
+ _state=state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s,%d of %d}",
+ getClass().getSimpleName(),
+ _state,
+ _contentPosition,
+ _contentLength);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* Event Handler interface
+ * These methods return true if the caller should process the events
+ * so far received (eg return from parseNext and call HttpChannel.handle).
+ * If multiple callbacks are called in sequence (eg
+ * headerComplete then messageComplete) from the same point in the parsing
+ * then it is sufficient for the caller to process the events only once.
+ */
+ public interface HttpHandler
+ {
+ public boolean content(T item);
+
+ public boolean headerComplete();
+
+ public boolean messageComplete();
+
+ /**
+ * This is the method called by parser when a HTTP Header name and value is found
+ * @param field The field parsed
+ * @return True if the parser should return to its caller
+ */
+ public boolean parsedHeader(HttpField field);
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that an EOF was received unexpectedly
+ * during the parsing of a HTTP message
+ */
+ public void earlyEOF();
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that a bad HTTP message has been received.
+ * @param status The bad status to send
+ * @param reason The textual reason for badness
+ */
+ public void badMessage(int status, String reason);
+
+ /* ------------------------------------------------------------ */
+ /** @return the size in bytes of the per parser header cache
+ */
+ public int getHeaderCacheSize();
+ }
+
+ public interface ProxyHandler
+ {
+ void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
+ }
+
+ public interface RequestHandler extends HttpHandler
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ * @param method The method as enum if of a known type
+ * @param methodString The method as a string
+ * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+ * @param version
+ * @return true if handling parsing should return.
+ */
+ public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
+
+ /**
+ * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+ * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+ * HttpHandler#headerComplete();
+ */
+ public abstract boolean parsedHostHeader(String host,int port);
+ }
+
+ public interface ResponseHandler extends HttpHandler
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ */
+ public abstract boolean startResponse(HttpVersion version, int status, String reason);
+ }
+
+ public Trie getFieldCache()
+ {
+ return _connectionFields;
+ }
+
+ private String getProxyField(ByteBuffer buffer)
+ {
+ _string.setLength(0);
+ _length=0;
+
+ while (buffer.hasRemaining())
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch<=' ')
+ return _string.toString();
+ _string.append((char)ch);
+ }
+ throw new BadMessage();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpScheme.java b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644
index 00000000..13f2a8d3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+ HTTP("http"),
+ HTTPS("https"),
+ WS("ws"),
+ WSS("wss");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpScheme version : HttpScheme.values())
+ CACHE.put(version.asString(),version);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpScheme(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asByteBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpStatus.java b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644
index 00000000..e6ea1f71
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
@@ -0,0 +1,1037 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ *
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ *
+ *
+ *
+ *
+ * Enum
+ * Code
+ * Message
+ *
+ * RFC 1945 - HTTP/1.0
+ *
+ * RFC 2616 - HTTP/1.1
+ *
+ * RFC 2518 - WEBDAV
+ *
+ *
+ *
+ * Informational - 1xx
+ * {@link #isInformational(int)}
+ *
+ *
+ *
+ * {@link #CONTINUE_100}
+ * 100
+ * Continue
+ *
+ *
+ * Sec. 10.1.1
+ *
+ *
+ *
+ * {@link #SWITCHING_PROTOCOLS_101}
+ * 101
+ * Switching Protocols
+ *
+ *
+ * Sec. 10.1.2
+ *
+ *
+ *
+ * {@link #PROCESSING_102}
+ * 102
+ * Processing
+ *
+ *
+ *
+ * Sec. 10.1
+ *
+ *
+ *
+ * Success - 2xx
+ * {@link #isSuccess(int)}
+ *
+ *
+ *
+ * {@link #OK_200}
+ * 200
+ * OK
+ *
+ * Sec. 9.2
+ *
+ * Sec. 10.2.1
+ *
+ *
+ *
+ * {@link #CREATED_201}
+ * 201
+ * Created
+ *
+ * Sec. 9.2
+ *
+ * Sec. 10.2.2
+ *
+ *
+ *
+ * {@link #ACCEPTED_202}
+ * 202
+ * Accepted
+ *
+ * Sec. 9.2
+ *
+ * Sec. 10.2.3
+ *
+ *
+ *
+ * {@link #NON_AUTHORITATIVE_INFORMATION_203}
+ * 203
+ * Non Authoritative Information
+ *
+ *
+ * Sec. 10.2.4
+ *
+ *
+ *
+ * {@link #NO_CONTENT_204}
+ * 204
+ * No Content
+ *
+ * Sec. 9.2
+ *
+ * Sec. 10.2.5
+ *
+ *
+ *
+ * {@link #RESET_CONTENT_205}
+ * 205
+ * Reset Content
+ *
+ *
+ * Sec. 10.2.6
+ *
+ *
+ *
+ * {@link #PARTIAL_CONTENT_206}
+ * 206
+ * Partial Content
+ *
+ *
+ * Sec. 10.2.7
+ *
+ *
+ *
+ * {@link #MULTI_STATUS_207}
+ * 207
+ * Multi-Status
+ *
+ *
+ *
+ * Sec. 10.2
+ *
+ *
+ *
+ * 207
+ * Partial Update OK
+ *
+ *
+ * draft/01
+ *
+ *
+ *
+ *
+ * Redirection - 3xx
+ * {@link #isRedirection(int)}
+ *
+ *
+ *
+ * {@link #MULTIPLE_CHOICES_300}
+ * 300
+ * Multiple Choices
+ *
+ * Sec. 9.3
+ *
+ * Sec. 10.3.1
+ *
+ *
+ *
+ * {@link #MOVED_PERMANENTLY_301}
+ * 301
+ * Moved Permanently
+ *
+ * Sec. 9.3
+ *
+ * Sec. 10.3.2
+ *
+ *
+ *
+ * {@link #MOVED_TEMPORARILY_302}
+ * 302
+ * Moved Temporarily
+ *
+ * Sec. 9.3
+ * (now "302 Found
")
+ *
+ *
+ *
+ * {@link #FOUND_302}
+ * 302
+ * Found
+ * (was "302 Moved Temporarily
")
+ *
+ * Sec. 10.3.3
+ *
+ *
+ *
+ * {@link #SEE_OTHER_303}
+ * 303
+ * See Other
+ *
+ *
+ * Sec. 10.3.4
+ *
+ *
+ *
+ * {@link #NOT_MODIFIED_304}
+ * 304
+ * Not Modified
+ *
+ * Sec. 9.3
+ *
+ * Sec. 10.3.5
+ *
+ *
+ *
+ * {@link #USE_PROXY_305}
+ * 305
+ * Use Proxy
+ *
+ *
+ * Sec. 10.3.6
+ *
+ *
+ *
+ *
+ * 306
+ * (Unused)
+ *
+ *
+ * Sec. 10.3.7
+ *
+ *
+ *
+ * {@link #TEMPORARY_REDIRECT_307}
+ * 307
+ * Temporary Redirect
+ *
+ *
+ * Sec. 10.3.8
+ *
+ *
+ *
+ *
+ * Client Error - 4xx
+ * {@link #isClientError(int)}
+ *
+ *
+ *
+ * {@link #BAD_REQUEST_400}
+ * 400
+ * Bad Request
+ *
+ * Sec. 9.4
+ *
+ * Sec. 10.4.1
+ *
+ *
+ *
+ * {@link #UNAUTHORIZED_401}
+ * 401
+ * Unauthorized
+ *
+ * Sec. 9.4
+ *
+ * Sec. 10.4.2
+ *
+ *
+ *
+ * {@link #PAYMENT_REQUIRED_402}
+ * 402
+ * Payment Required
+ *
+ * Sec. 9.4
+ *
+ * Sec. 10.4.3
+ *
+ *
+ *
+ * {@link #FORBIDDEN_403}
+ * 403
+ * Forbidden
+ *
+ * Sec. 9.4
+ *
+ * Sec. 10.4.4
+ *
+ *
+ *
+ * {@link #NOT_FOUND_404}
+ * 404
+ * Not Found
+ *
+ * Sec. 9.4
+ *
+ * Sec. 10.4.5
+ *
+ *
+ *
+ * {@link #METHOD_NOT_ALLOWED_405}
+ * 405
+ * Method Not Allowed
+ *
+ *
+ * Sec. 10.4.6
+ *
+ *
+ *
+ * {@link #NOT_ACCEPTABLE_406}
+ * 406
+ * Not Acceptable
+ *
+ *
+ * Sec. 10.4.7
+ *
+ *
+ *
+ * {@link #PROXY_AUTHENTICATION_REQUIRED_407}
+ * 407
+ * Proxy Authentication Required
+ *
+ *
+ * Sec. 10.4.8
+ *
+ *
+ *
+ * {@link #REQUEST_TIMEOUT_408}
+ * 408
+ * Request Timeout
+ *
+ *
+ * Sec. 10.4.9
+ *
+ *
+ *
+ * {@link #CONFLICT_409}
+ * 409
+ * Conflict
+ *
+ *
+ * Sec. 10.4.10
+ *
+ *
+ *
+ *
+ * {@link #GONE_410}
+ * 410
+ * Gone
+ *
+ *
+ * Sec. 10.4.11
+ *
+ *
+ *
+ *
+ * {@link #LENGTH_REQUIRED_411}
+ * 411
+ * Length Required
+ *
+ *
+ * Sec. 10.4.12
+ *
+ *
+ *
+ *
+ * {@link #PRECONDITION_FAILED_412}
+ * 412
+ * Precondition Failed
+ *
+ *
+ * Sec. 10.4.13
+ *
+ *
+ *
+ *
+ * {@link #REQUEST_ENTITY_TOO_LARGE_413}
+ * 413
+ * Request Entity Too Large
+ *
+ *
+ * Sec. 10.4.14
+ *
+ *
+ *
+ *
+ * {@link #REQUEST_URI_TOO_LONG_414}
+ * 414
+ * Request-URI Too Long
+ *
+ *
+ * Sec. 10.4.15
+ *
+ *
+ *
+ *
+ * {@link #UNSUPPORTED_MEDIA_TYPE_415}
+ * 415
+ * Unsupported Media Type
+ *
+ *
+ * Sec. 10.4.16
+ *
+ *
+ *
+ *
+ * {@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}
+ * 416
+ * Requested Range Not Satisfiable
+ *
+ *
+ * Sec. 10.4.17
+ *
+ *
+ *
+ *
+ * {@link #EXPECTATION_FAILED_417}
+ * 417
+ * Expectation Failed
+ *
+ *
+ * Sec. 10.4.18
+ *
+ *
+ *
+ *
+ *
+ * 418
+ * Reauthentication Required
+ *
+ *
+ * draft/01
+ *
+ *
+ *
+ *
+ * 418
+ * Unprocessable Entity
+ *
+ *
+ *
+ * draft/05
+ *
+ *
+ *
+ * 419
+ * Proxy Reauthentication Required
+ *
+ *
+ * draft/01
+ *
+ *
+ *
+ *
+ * 419
+ * Insufficient Space on Resource
+ *
+ *
+ *
+ * draft/05
+ *
+ *
+ *
+ * 420
+ * Method Failure
+ *
+ *
+ *
+ * draft/05
+ *
+ *
+ *
+ * 421
+ * (Unused)
+ *
+ *
+ *
+ *
+ *
+ * {@link #UNPROCESSABLE_ENTITY_422}
+ * 422
+ * Unprocessable Entity
+ *
+ *
+ *
+ * Sec. 10.3
+ *
+ *
+ * {@link #LOCKED_423}
+ * 423
+ * Locked
+ *
+ *
+ *
+ * Sec. 10.4
+ *
+ *
+ * {@link #FAILED_DEPENDENCY_424}
+ * 424
+ * Failed Dependency
+ *
+ *
+ *
+ * Sec. 10.5
+ *
+ *
+ *
+ * Server Error - 5xx
+ * {@link #isServerError(int)}
+ *
+ *
+ *
+ * {@link #INTERNAL_SERVER_ERROR_500}
+ * 500
+ * Internal Server Error
+ *
+ * Sec. 9.5
+ *
+ * Sec. 10.5.1
+ *
+ *
+ *
+ * {@link #NOT_IMPLEMENTED_501}
+ * 501
+ * Not Implemented
+ *
+ * Sec. 9.5
+ *
+ * Sec. 10.5.2
+ *
+ *
+ *
+ * {@link #BAD_GATEWAY_502}
+ * 502
+ * Bad Gateway
+ *
+ * Sec. 9.5
+ *
+ * Sec. 10.5.3
+ *
+ *
+ *
+ * {@link #SERVICE_UNAVAILABLE_503}
+ * 503
+ * Service Unavailable
+ *
+ * Sec. 9.5
+ *
+ * Sec. 10.5.4
+ *
+ *
+ *
+ * {@link #GATEWAY_TIMEOUT_504}
+ * 504
+ * Gateway Timeout
+ *
+ *
+ * Sec. 10.5.5
+ *
+ *
+ *
+ * {@link #HTTP_VERSION_NOT_SUPPORTED_505}
+ * 505
+ * HTTP Version Not Supported
+ *
+ *
+ * Sec. 10.5.6
+ *
+ *
+ *
+ *
+ * 506
+ * (Unused)
+ *
+ *
+ *
+ *
+ *
+ * {@link #INSUFFICIENT_STORAGE_507}
+ * 507
+ * Insufficient Storage
+ *
+ *
+ *
+ * Sec. 10.6
+ *
+ *
+ *
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+ public final static int NOT_SET_000 = 0;
+ public final static int CONTINUE_100 = 100;
+ public final static int SWITCHING_PROTOCOLS_101 = 101;
+ public final static int PROCESSING_102 = 102;
+
+ public final static int OK_200 = 200;
+ public final static int CREATED_201 = 201;
+ public final static int ACCEPTED_202 = 202;
+ public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+ public final static int NO_CONTENT_204 = 204;
+ public final static int RESET_CONTENT_205 = 205;
+ public final static int PARTIAL_CONTENT_206 = 206;
+ public final static int MULTI_STATUS_207 = 207;
+
+ public final static int MULTIPLE_CHOICES_300 = 300;
+ public final static int MOVED_PERMANENTLY_301 = 301;
+ public final static int MOVED_TEMPORARILY_302 = 302;
+ public final static int FOUND_302 = 302;
+ public final static int SEE_OTHER_303 = 303;
+ public final static int NOT_MODIFIED_304 = 304;
+ public final static int USE_PROXY_305 = 305;
+ public final static int TEMPORARY_REDIRECT_307 = 307;
+
+ public final static int BAD_REQUEST_400 = 400;
+ public final static int UNAUTHORIZED_401 = 401;
+ public final static int PAYMENT_REQUIRED_402 = 402;
+ public final static int FORBIDDEN_403 = 403;
+ public final static int NOT_FOUND_404 = 404;
+ public final static int METHOD_NOT_ALLOWED_405 = 405;
+ public final static int NOT_ACCEPTABLE_406 = 406;
+ public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+ public final static int REQUEST_TIMEOUT_408 = 408;
+ public final static int CONFLICT_409 = 409;
+ public final static int GONE_410 = 410;
+ public final static int LENGTH_REQUIRED_411 = 411;
+ public final static int PRECONDITION_FAILED_412 = 412;
+ public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+ public final static int REQUEST_URI_TOO_LONG_414 = 414;
+ public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+ public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+ public final static int EXPECTATION_FAILED_417 = 417;
+ public final static int UNPROCESSABLE_ENTITY_422 = 422;
+ public final static int LOCKED_423 = 423;
+ public final static int FAILED_DEPENDENCY_424 = 424;
+
+ public final static int INTERNAL_SERVER_ERROR_500 = 500;
+ public final static int NOT_IMPLEMENTED_501 = 501;
+ public final static int BAD_GATEWAY_502 = 502;
+ public final static int SERVICE_UNAVAILABLE_503 = 503;
+ public final static int GATEWAY_TIMEOUT_504 = 504;
+ public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+ public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+ public static final int MAX_CODE = 507;
+
+
+ private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+ static
+ {
+ for (Code code : Code.values())
+ {
+ codeMap[code._code] = code;
+ }
+ }
+
+
+ public enum Code
+ {
+ /*
+ * --------------------------------------------------------------------
+ * Informational messages in 1xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 100 Continue
*/
+ CONTINUE(CONTINUE_100, "Continue"),
+ /** 101 Switching Protocols
*/
+ SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+ /** 102 Processing
*/
+ PROCESSING(PROCESSING_102, "Processing"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+ * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 200 OK
*/
+ OK(OK_200, "OK"),
+ /** 201 Created
*/
+ CREATED(CREATED_201, "Created"),
+ /** 202 Accepted
*/
+ ACCEPTED(ACCEPTED_202, "Accepted"),
+ /** 203 Non Authoritative Information
*/
+ NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+ /** 204 No Content
*/
+ NO_CONTENT(NO_CONTENT_204, "No Content"),
+ /** 205 Reset Content
*/
+ RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+ /** 206 Partial Content
*/
+ PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+ /** 207 Multi-Status
*/
+ MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1
+ */
+
+ /** 300 Mutliple Choices
*/
+ MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+ /** 301 Moved Permanently
*/
+ MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+ /** 302 Moved Temporarily
*/
+ MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+ /** 302 Found
*/
+ FOUND(FOUND_302, "Found"),
+ /** 303 See Other
*/
+ SEE_OTHER(SEE_OTHER_303, "See Other"),
+ /** 304 Not Modified
*/
+ NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+ /** 305 Use Proxy
*/
+ USE_PROXY(USE_PROXY_305, "Use Proxy"),
+ /** 307 Temporary Redirect
*/
+ TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 400 Bad Request
*/
+ BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+ /** 401 Unauthorized
*/
+ UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+ /** 402 Payment Required
*/
+ PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+ /** 403 Forbidden
*/
+ FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+ /** 404 Not Found
*/
+ NOT_FOUND(NOT_FOUND_404, "Not Found"),
+ /** 405 Method Not Allowed
*/
+ METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+ /** 406 Not Acceptable
*/
+ NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+ /** 407 Proxy Authentication Required
*/
+ PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+ /** 408 Request Timeout
*/
+ REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+ /** 409 Conflict
*/
+ CONFLICT(CONFLICT_409, "Conflict"),
+ /** 410 Gone
*/
+ GONE(GONE_410, "Gone"),
+ /** 411 Length Required
*/
+ LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+ /** 412 Precondition Failed
*/
+ PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+ /** 413 Request Entity Too Large
*/
+ REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+ /** 414 Request-URI Too Long
*/
+ REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+ /** 415 Unsupported Media Type
*/
+ UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+ /** 416 Requested Range Not Satisfiable
*/
+ REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+ /** 417 Expectation Failed
*/
+ EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+ /** 422 Unprocessable Entity
*/
+ UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+ /** 423 Locked
*/
+ LOCKED(LOCKED_423, "Locked"),
+ /** 424 Failed Dependency
*/
+ FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** 500 Server Error
*/
+ INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+ /** 501 Not Implemented
*/
+ NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+ /** 502 Bad Gateway
*/
+ BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+ /** 503 Service Unavailable
*/
+ SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+ /** 504 Gateway Timeout
*/
+ GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+ /** 505 HTTP Version Not Supported
*/
+ HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+ /** 507 Insufficient Storage
*/
+ INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage");
+
+ private final int _code;
+ private final String _message;
+
+ private Code(int code, String message)
+ {
+ this._code = code;
+ _message=message;
+ }
+
+ public int getCode()
+ {
+ return _code;
+ }
+
+ public String getMessage()
+ {
+ return _message;
+ }
+
+
+ public boolean equals(int code)
+ {
+ return (this._code == code);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("[%03d %s]",this._code,this.getMessage());
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Informational
message category as defined in the RFC 1945 - HTTP/1.0 ,
+ * and RFC 2616 -
+ * HTTP/1.1 .
+ *
+ * @return true if within range of codes that belongs to
+ * Informational
messages.
+ */
+ public boolean isInformational()
+ {
+ return HttpStatus.isInformational(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success
message category as defined in the RFC 1945 - HTTP/1.0 ,
+ * and RFC 2616 -
+ * HTTP/1.1 .
+ *
+ * @return true if within range of codes that belongs to
+ * Success
messages.
+ */
+ public boolean isSuccess()
+ {
+ return HttpStatus.isSuccess(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection
message category as defined in the RFC 1945 - HTTP/1.0 ,
+ * and RFC 2616 -
+ * HTTP/1.1 .
+ *
+ * @return true if within range of codes that belongs to
+ * Redirection
messages.
+ */
+ public boolean isRedirection()
+ {
+ return HttpStatus.isRedirection(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error
message category as defined in the RFC 1945 - HTTP/1.0 ,
+ * and RFC 2616 -
+ * HTTP/1.1 .
+ *
+ * @return true if within range of codes that belongs to
+ * Client Error
messages.
+ */
+ public boolean isClientError()
+ {
+ return HttpStatus.isClientError(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error
message category as defined in the RFC 1945 - HTTP/1.0 ,
+ * and RFC 2616 -
+ * HTTP/1.1 .
+ *
+ * @return true if within range of codes that belongs to
+ * Server Error
messages.
+ */
+ public boolean isServerError()
+ {
+ return HttpStatus.isServerError(this._code);
+ }
+ }
+
+
+ /**
+ * Get the HttpStatusCode for a specific code
+ *
+ * @param code
+ * the code to lookup.
+ * @return the {@link HttpStatus} if found, or null if not found.
+ */
+ public static Code getCode(int code)
+ {
+ if (code <= MAX_CODE)
+ {
+ return codeMap[code];
+ }
+ return null;
+ }
+
+ /**
+ * Get the status message for a specific code.
+ *
+ * @param code
+ * the code to look up
+ * @return the specific message, or the code number itself if code
+ * does not match known list.
+ */
+ public static String getMessage(int code)
+ {
+ Code codeEnum = getCode(code);
+ if (codeEnum != null)
+ {
+ return codeEnum.getMessage();
+ }
+ else
+ {
+ return Integer.toString(code);
+ }
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Informational
message category as defined in the RFC 1945 - HTTP/1.0 , and RFC 2616 - HTTP/1.1 .
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Informational
messages.
+ */
+ public static boolean isInformational(int code)
+ {
+ return ((100 <= code) && (code <= 199));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success
message category as defined in the RFC 1945 - HTTP/1.0 , and RFC 2616 - HTTP/1.1 .
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Success
messages.
+ */
+ public static boolean isSuccess(int code)
+ {
+ return ((200 <= code) && (code <= 299));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection
message category as defined in the RFC 1945 - HTTP/1.0 , and RFC 2616 - HTTP/1.1 .
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Redirection
messages.
+ */
+ public static boolean isRedirection(int code)
+ {
+ return ((300 <= code) && (code <= 399));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error
message category as defined in the RFC 1945 - HTTP/1.0 , and RFC 2616 - HTTP/1.1 .
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Client Error
messages.
+ */
+ public static boolean isClientError(int code)
+ {
+ return ((400 <= code) && (code <= 499));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error
message category as defined in the RFC 1945 - HTTP/1.0 , and RFC 2616 - HTTP/1.1 .
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Server Error
messages.
+ */
+ public static boolean isServerError(int code)
+ {
+ return ((500 <= code) && (code <= 599));
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTester.java b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
new file mode 100644
index 00000000..537c457c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
@@ -0,0 +1,367 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpGenerator.RequestInfo;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+public class HttpTester
+{
+ private HttpTester()
+ {
+ }
+
+ public static Request newRequest()
+ {
+ return new Request();
+ }
+
+ public static Request parseRequest(String request)
+ {
+ Request r=new Request();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(BufferUtil.toBuffer(request));
+ return r;
+ }
+
+ public static Request parseRequest(ByteBuffer request)
+ {
+ Request r=new Request();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(request);
+ return r;
+ }
+
+ public static Response parseResponse(String response)
+ {
+ Response r=new Response();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(BufferUtil.toBuffer(response));
+ return r;
+ }
+
+ public static Response parseResponse(ByteBuffer response)
+ {
+ Response r=new Response();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(response);
+ return r;
+ }
+
+
+ public abstract static class Message extends HttpFields implements HttpParser.HttpHandler
+ {
+ ByteArrayOutputStream _content;
+ HttpVersion _version=HttpVersion.HTTP_1_0;
+
+ public HttpVersion getVersion()
+ {
+ return _version;
+ }
+
+ public void setVersion(String version)
+ {
+ setVersion(HttpVersion.CACHE.get(version));
+ }
+
+ public void setVersion(HttpVersion version)
+ {
+ _version=version;
+ }
+
+ public void setContent(byte[] bytes)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(bytes);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setContent(String content)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(StringUtil.getBytes(content));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setContent(ByteBuffer content)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(BufferUtil.toArray(content));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ @Override
+ public boolean parsedHeader(HttpField field)
+ {
+ put(field.getName(),field.getValue());
+ return false;
+ }
+
+ @Override
+ public boolean messageComplete()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ _content=new ByteArrayOutputStream();
+ return false;
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ }
+
+ @Override
+ public boolean content(ByteBuffer ref)
+ {
+ try
+ {
+ _content.write(BufferUtil.toArray(ref));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return false;
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ throw new RuntimeException(reason);
+ }
+
+ public ByteBuffer generate()
+ {
+ try
+ {
+ HttpGenerator generator = new HttpGenerator();
+ HttpGenerator.Info info = getInfo();
+ // System.err.println(info.getClass());
+ // System.err.println(info);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteBuffer header=null;
+ ByteBuffer chunk=null;
+ ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
+
+
+ loop: while(!generator.isEnd())
+ {
+ HttpGenerator.Result result = info instanceof RequestInfo
+ ?generator.generateRequest((RequestInfo)info,header,chunk,content,true)
+ :generator.generateResponse((ResponseInfo)info,header,chunk,content,true);
+ switch(result)
+ {
+ case NEED_HEADER:
+ header=BufferUtil.allocate(8192);
+ continue;
+
+ case NEED_CHUNK:
+ chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+ continue;
+
+ case NEED_INFO:
+ throw new IllegalStateException();
+
+ case FLUSH:
+ if (BufferUtil.hasContent(header))
+ {
+ out.write(BufferUtil.toArray(header));
+ BufferUtil.clear(header);
+ }
+ if (BufferUtil.hasContent(chunk))
+ {
+ out.write(BufferUtil.toArray(chunk));
+ BufferUtil.clear(chunk);
+ }
+ if (BufferUtil.hasContent(content))
+ {
+ out.write(BufferUtil.toArray(content));
+ BufferUtil.clear(content);
+ }
+ break;
+
+ case SHUTDOWN_OUT:
+ break loop;
+ }
+ }
+
+ return ByteBuffer.wrap(out.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+ abstract public HttpGenerator.Info getInfo();
+
+ @Override
+ public int getHeaderCacheSize()
+ {
+ return 0;
+ }
+
+ }
+
+ public static class Request extends Message implements HttpParser.RequestHandler
+ {
+ private String _method;
+ private String _uri;
+
+ @Override
+ public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
+ {
+ _method=methodString;
+ _uri=BufferUtil.toUTF8String(uri);
+ _version=version;
+ return false;
+ }
+
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ public String getUri()
+ {
+ return _uri;
+ }
+
+ public void setMethod(String method)
+ {
+ _method=method;
+ }
+
+ public void setURI(String uri)
+ {
+ _uri=uri;
+ }
+
+ @Override
+ public HttpGenerator.RequestInfo getInfo()
+ {
+ return new HttpGenerator.RequestInfo(_version,this,_content==null?0:_content.size(),_method,_uri);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
+ }
+
+ public void setHeader(String name, String value)
+ {
+ put(name,value);
+ }
+
+ @Override
+ public boolean parsedHostHeader(String host,int port)
+ {
+ return false;
+ }
+ }
+
+ public static class Response extends Message implements HttpParser.ResponseHandler
+ {
+ private int _status;
+ private String _reason;
+
+ @Override
+ public boolean startResponse(HttpVersion version, int status, String reason)
+ {
+ _version=version;
+ _status=status;
+ _reason=reason;
+ return false;
+ }
+
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ public byte[] getContentBytes()
+ {
+ if (_content==null)
+ return null;
+ return _content.toByteArray();
+ }
+
+ public String getContent()
+ {
+ if (_content==null)
+ return null;
+ byte[] bytes=_content.toByteArray();
+
+ String content_type=get(HttpHeader.CONTENT_TYPE);
+ String encoding=MimeTypes.getCharsetFromContentType(content_type);
+ Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
+
+ return new String(bytes,charset);
+ }
+
+ @Override
+ public HttpGenerator.ResponseInfo getInfo()
+ {
+ return new HttpGenerator.ResponseInfo(_version,this,_content==null?-1:_content.size(),_status,_reason,false);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTokens.java b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644
index 00000000..4138334d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+ // Terminal symbols.
+ static final byte COLON= (byte)':';
+ static final byte TAB= 0x09;
+ static final byte LINE_FEED= 0x0A;
+ static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte SPACE= 0x20;
+ static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+ static final byte SEMI_COLON= (byte)';';
+
+ public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpURI.java b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
new file mode 100644
index 00000000..afe925e1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
@@ -0,0 +1,784 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array. Given a URI
+ * http://user@host:port/path/info;param?query#fragment
+ * this class will split it into the following undecoded optional elements:
+ * {@link #getScheme()} - http:
+ * {@link #getAuthority()} - //name@host:port
+ * {@link #getHost()} - host
+ * {@link #getPort()} - port
+ * {@link #getPath()} - /path/info
+ * {@link #getParam()} - param
+ * {@link #getQuery()} - query
+ * {@link #getFragment()} - fragment
+ *
+ *
+ */
+public class HttpURI
+{
+ private static final byte[] __empty={};
+ private final static int
+ START=0,
+ AUTH_OR_PATH=1,
+ SCHEME_OR_PATH=2,
+ AUTH=4,
+ IPV6=5,
+ PORT=6,
+ PATH=7,
+ PARAM=8,
+ QUERY=9,
+ ASTERISK=10;
+
+ final Charset _charset;
+ boolean _partial=false;
+ byte[] _raw=__empty;
+ String _rawString;
+ int _scheme;
+ int _authority;
+ int _host;
+ int _port;
+ int _portValue;
+ int _path;
+ int _param;
+ int _query;
+ int _fragment;
+ int _end;
+ boolean _encoded=false;
+
+ public HttpURI()
+ {
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(Charset charset)
+ {
+ _charset = charset;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
+ */
+ public HttpURI(boolean parsePartialAuth)
+ {
+ _partial=parsePartialAuth;
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(String raw)
+ {
+ _rawString=raw;
+ byte[] b = raw.getBytes(StandardCharsets.UTF_8);
+ parse(b,0,b.length);
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(byte[] raw,int offset, int length)
+ {
+ parse2(raw,offset,length);
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(URI uri)
+ {
+ parse(uri.toASCIIString());
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public void parse(String raw)
+ {
+ byte[] b = StringUtil.getUtf8Bytes(raw);
+ parse2(b,0,b.length);
+ _rawString=raw;
+ }
+
+ public void parseConnect(String raw)
+ {
+ byte[] b = StringUtil.getBytes(raw);
+ parseConnect(b,0,b.length);
+ _rawString=raw;
+ }
+
+ public void parse(byte[] raw,int offset, int length)
+ {
+ _rawString=null;
+ parse2(raw,offset,length);
+ }
+
+
+ public void parseConnect(byte[] raw,int offset, int length)
+ {
+ _rawString=null;
+ _encoded=false;
+ _raw=raw;
+ int i=offset;
+ int e=offset+length;
+ int state=AUTH;
+ _end=offset+length;
+ _scheme=offset;
+ _authority=offset;
+ _host=offset;
+ _port=_end;
+ _portValue=-1;
+ _path=_end;
+ _param=_end;
+ _query=_end;
+ _fragment=_end;
+
+ loop: while (i6 && c=='t')
+ {
+ if (_raw[offset+3]==':')
+ {
+ s=offset+3;
+ i=offset+4;
+ c=':';
+ }
+ else if (_raw[offset+4]==':')
+ {
+ s=offset+4;
+ i=offset+5;
+ c=':';
+ }
+ else if (_raw[offset+5]==':')
+ {
+ s=offset+5;
+ i=offset+6;
+ c=':';
+ }
+ }
+
+ switch (c)
+ {
+ case ':':
+ {
+ m = i++;
+ _authority = m;
+ _path = m;
+ c = (char)(0xff & _raw[i]);
+ if (c == '/')
+ state = AUTH_OR_PATH;
+ else
+ {
+ _host = m;
+ _port = m;
+ state = PATH;
+ }
+ break;
+ }
+
+ case '/':
+ {
+ state = PATH;
+ break;
+ }
+
+ case ';':
+ {
+ _param = s;
+ state = PARAM;
+ break;
+ }
+
+ case '?':
+ {
+ _param = s;
+ _query = s;
+ state = QUERY;
+ break;
+ }
+
+ case '#':
+ {
+ _param = s;
+ _query = s;
+ _fragment = s;
+ break;
+ }
+ }
+ continue;
+ }
+
+ case AUTH:
+ {
+ switch (c)
+ {
+
+ case '/':
+ {
+ m = s;
+ _path = m;
+ _port = _path;
+ state = PATH;
+ break;
+ }
+ case '@':
+ {
+ _host = i;
+ break;
+ }
+ case ':':
+ {
+ _port = s;
+ state = PORT;
+ break;
+ }
+ case '[':
+ {
+ state = IPV6;
+ break;
+ }
+ }
+ continue;
+ }
+
+ case IPV6:
+ {
+ switch (c)
+ {
+ case '/':
+ {
+ throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+ }
+ case ']':
+ {
+ state = AUTH;
+ break;
+ }
+ }
+
+ continue;
+ }
+
+ case PORT:
+ {
+ if (c=='/')
+ {
+ m=s;
+ _path=m;
+ if (_port<=_authority)
+ _port=_path;
+ state=PATH;
+ }
+ continue;
+ }
+
+ case PATH:
+ {
+ switch (c)
+ {
+ case ';':
+ {
+ _param = s;
+ state = PARAM;
+ break;
+ }
+ case '?':
+ {
+ _param = s;
+ _query = s;
+ state = QUERY;
+ break;
+ }
+ case '#':
+ {
+ _param = s;
+ _query = s;
+ _fragment = s;
+ break state;
+ }
+ case '%':
+ {
+ _encoded=true;
+ }
+ }
+ continue;
+ }
+
+ case PARAM:
+ {
+ switch (c)
+ {
+ case '?':
+ {
+ _query = s;
+ state = QUERY;
+ break;
+ }
+ case '#':
+ {
+ _query = s;
+ _fragment = s;
+ break state;
+ }
+ }
+ continue;
+ }
+
+ case QUERY:
+ {
+ if (c=='#')
+ {
+ _fragment=s;
+ break state;
+ }
+ continue;
+ }
+
+ case ASTERISK:
+ {
+ throw new IllegalArgumentException("only '*'");
+ }
+ }
+ }
+
+ if (_port<_path)
+ _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+ }
+
+ public String getScheme()
+ {
+ if (_scheme==_authority)
+ return null;
+ int l=_authority-_scheme;
+ if (l==5 &&
+ _raw[_scheme]=='h' &&
+ _raw[_scheme+1]=='t' &&
+ _raw[_scheme+2]=='t' &&
+ _raw[_scheme+3]=='p' )
+ return HttpScheme.HTTP.asString();
+ if (l==6 &&
+ _raw[_scheme]=='h' &&
+ _raw[_scheme+1]=='t' &&
+ _raw[_scheme+2]=='t' &&
+ _raw[_scheme+3]=='p' &&
+ _raw[_scheme+4]=='s' )
+ return HttpScheme.HTTPS.asString();
+
+ return new String(_raw,_scheme,_authority-_scheme-1,_charset);
+ }
+
+ public String getAuthority()
+ {
+ if (_authority==_path)
+ return null;
+ return new String(_raw,_authority,_path-_authority,_charset);
+ }
+
+ public String getHost()
+ {
+ if (_host==_port)
+ return null;
+ if (_raw[_host]=='[')
+ return new String(_raw,_host+1,_port-_host-2,_charset);
+ return new String(_raw,_host,_port-_host,_charset);
+ }
+
+ public int getPort()
+ {
+ return _portValue;
+ }
+
+ public String getPath()
+ {
+ if (_path==_param)
+ return null;
+ return new String(_raw,_path,_param-_path,_charset);
+ }
+
+ public String getDecodedPath()
+ {
+ if (_path==_param)
+ return null;
+
+ Utf8StringBuilder utf8b=null;
+
+ for (int i=_path;i<_param;i++)
+ {
+ byte b = _raw[i];
+
+ if (b=='%')
+ {
+ if (utf8b==null)
+ {
+ utf8b=new Utf8StringBuilder();
+ utf8b.append(_raw,_path,i-_path);
+ }
+
+ if ((i+2)>=_param)
+ throw new IllegalArgumentException("Bad % encoding: "+this);
+ if (_raw[i+1]=='u')
+ {
+ if ((i+5)>=_param)
+ throw new IllegalArgumentException("Bad %u encoding: "+this);
+ try
+ {
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+ utf8b.getStringBuilder().append(unicode);
+ i+=5;
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ else
+ {
+ b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+ utf8b.append(b);
+ i+=2;
+ }
+ continue;
+ }
+ else if (utf8b!=null)
+ {
+ utf8b.append(b);
+ }
+ }
+
+ if (utf8b==null)
+ return StringUtil.toUTF8String(_raw, _path, _param-_path);
+ return utf8b.toString();
+ }
+
+ public String getDecodedPath(String encoding)
+ {
+ return getDecodedPath(Charset.forName(encoding));
+ }
+
+ public String getDecodedPath(Charset encoding)
+ {
+ if (_path==_param)
+ return null;
+
+ int length = _param-_path;
+ byte[] bytes=null;
+ int n=0;
+
+ for (int i=_path;i<_param;i++)
+ {
+ byte b = _raw[i];
+
+ if (b=='%')
+ {
+ if (bytes==null)
+ {
+ bytes=new byte[length];
+ System.arraycopy(_raw,_path,bytes,0,n);
+ }
+
+ if ((i+2)>=_param)
+ throw new IllegalArgumentException("Bad % encoding: "+this);
+ if (_raw[i+1]=='u')
+ {
+ if ((i+5)>=_param)
+ throw new IllegalArgumentException("Bad %u encoding: "+this);
+
+ try
+ {
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+ byte[] encoded = unicode.getBytes(encoding);
+ System.arraycopy(encoded,0,bytes,n,encoded.length);
+ n+=encoded.length;
+ i+=5;
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ else
+ {
+ b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+ bytes[n++]=b;
+ i+=2;
+ }
+ continue;
+ }
+ else if (bytes==null)
+ {
+ n++;
+ continue;
+ }
+
+ bytes[n++]=b;
+ }
+
+
+ if (bytes==null)
+ return new String(_raw,_path,_param-_path,encoding);
+
+ return new String(bytes,0,n,encoding);
+ }
+
+ public String getPathAndParam()
+ {
+ if (_path==_query)
+ return null;
+ return new String(_raw,_path,_query-_path,_charset);
+ }
+
+ public String getCompletePath()
+ {
+ if (_path==_end)
+ return null;
+ return new String(_raw,_path,_end-_path,_charset);
+ }
+
+ public String getParam()
+ {
+ if (_param==_query)
+ return null;
+ return new String(_raw,_param+1,_query-_param-1,_charset);
+ }
+
+ public String getQuery()
+ {
+ if (_query==_fragment)
+ return null;
+ return new String(_raw,_query+1,_fragment-_query-1,_charset);
+ }
+
+ public String getQuery(String encoding)
+ {
+ if (_query==_fragment)
+ return null;
+ return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
+ }
+
+ public boolean hasQuery()
+ {
+ return (_fragment>_query);
+ }
+
+ public String getFragment()
+ {
+ if (_fragment==_end)
+ return null;
+ return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
+ }
+
+ public void decodeQueryTo(MultiMap parameters)
+ {
+ if (_query==_fragment)
+ return;
+ if (_charset.equals(StandardCharsets.UTF_8))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
+ }
+
+ public void decodeQueryTo(MultiMap parameters, String encoding) throws UnsupportedEncodingException
+ {
+ if (_query==_fragment)
+ return;
+
+ if (encoding==null || StringUtil.isUTF8(encoding))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+ }
+
+ public void decodeQueryTo(MultiMap parameters, Charset encoding) throws UnsupportedEncodingException
+ {
+ if (_query==_fragment)
+ return;
+
+ if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+ }
+
+ public void clear()
+ {
+ _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
+ _raw=__empty;
+ _rawString="";
+ _encoded=false;
+ }
+
+ @Override
+ public String toString()
+ {
+ if (_rawString==null)
+ _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
+ return _rawString;
+ }
+
+ public void writeTo(Utf8StringBuilder buf)
+ {
+ buf.append(_raw,_scheme,_end-_scheme);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpVersion.java b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644
index 00000000..eb889e56
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+ HTTP_0_9("HTTP/0.9",9),
+ HTTP_1_0("HTTP/1.0",10),
+ HTTP_1_1("HTTP/1.1",11),
+ HTTP_2_0("HTTP/2.0",20);
+
+ /* ------------------------------------------------------------ */
+ public final static Trie CACHE= new ArrayTrie();
+ static
+ {
+ for (HttpVersion version : HttpVersion.values())
+ CACHE.put(version.toString(),version);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a Http Version and whitespace in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+ {
+ int length=limit-position;
+ if (length<9)
+ return null;
+
+ if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+ ((bytes[position]=='H' && bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+ (bytes[position]=='h' && bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+ {
+ switch(bytes[position+5])
+ {
+ case '1':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_1_0;
+ case '1':
+ return HTTP_1_1;
+ }
+ break;
+ case '2':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_2_0;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters
+ * @return A HttpVersion if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+ return null;
+ }
+
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final ByteBuffer _buffer;
+ private final int _version;
+
+ /* ------------------------------------------------------------ */
+ HttpVersion(String s,int version)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _buffer=ByteBuffer.wrap(_bytes);
+ _version=version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] toBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /**
+ * Case insensitive fromString() conversion
+ * @param version the String to convert to enum constant
+ * @return the enum constant or null if version unknown
+ */
+ public static HttpVersion fromString(String version)
+ {
+ return CACHE.get(version);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static HttpVersion fromVersion(int version)
+ {
+ switch(version)
+ {
+ case 9: return HttpVersion.HTTP_0_9;
+ case 10: return HttpVersion.HTTP_1_0;
+ case 11: return HttpVersion.HTTP_1_1;
+ default: throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/MimeTypes.java b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644
index 00000000..9cac4abd
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
@@ -0,0 +1,485 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class MimeTypes
+{
+ public enum Type
+ {
+ FORM_ENCODED("application/x-www-form-urlencoded"),
+ MESSAGE_HTTP("message/http"),
+ MULTIPART_BYTERANGES("multipart/byteranges"),
+
+ TEXT_HTML("text/html"),
+ TEXT_PLAIN("text/plain"),
+ TEXT_XML("text/xml"),
+ TEXT_JSON("text/json",StandardCharsets.UTF_8),
+ APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
+
+ TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
+ TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
+
+ TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
+ TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
+
+ TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
+ TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
+
+ TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
+ TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
+
+ APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
+ APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
+
+
+ /* ------------------------------------------------------------ */
+ private final String _string;
+ private final Type _base;
+ private final ByteBuffer _buffer;
+ private final Charset _charset;
+ private final boolean _assumedCharset;
+ private final HttpField _field;
+
+ /* ------------------------------------------------------------ */
+ Type(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=this;
+ _charset=null;
+ _assumedCharset=false;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Type base)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=base;
+ int i=s.indexOf("; charset=");
+ _charset=Charset.forName(s.substring(i+10));
+ _assumedCharset=false;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Charset cs)
+ {
+ _string=s;
+ _base=this;
+ _buffer=BufferUtil.toBuffer(s);
+ _charset=cs;
+ _assumedCharset=true;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Charset getCharset()
+ {
+ return _charset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCharsetAssumed()
+ {
+ return _assumedCharset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpField getContentTypeField()
+ {
+ return _field;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Type getBaseType()
+ {
+ return _base;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private static final Logger LOG = Log.getLogger(MimeTypes.class);
+ public final static Trie CACHE= new ArrayTrie<>(512);
+ private final static Trie TYPES= new ArrayTrie(512);
+ private final static Map __dftMimeMap = new HashMap();
+ private final static Map __encodings = new HashMap();
+
+ static
+ {
+ for (MimeTypes.Type type : MimeTypes.Type.values())
+ {
+ CACHE.put(type.toString(),type);
+ TYPES.put(type.toString(),type.asBuffer());
+
+ int charset=type.toString().indexOf(";charset=");
+ if (charset>0)
+ {
+ CACHE.put(type.toString().replace(";charset=","; charset="),type);
+ TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
+ }
+ }
+
+ try
+ {
+ ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+ Enumeration i = mime.getKeys();
+ while(i.hasMoreElements())
+ {
+ String ext = i.nextElement();
+ String m = mime.getString(ext);
+ __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+
+ try
+ {
+ ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+ Enumeration i = encoding.getKeys();
+ while(i.hasMoreElements())
+ {
+ String type = i.nextElement();
+ __encodings.put(type,encoding.getString(type));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private final Map _mimeMap=new HashMap();
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public MimeTypes()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Map getMimeMap()
+ {
+ return _mimeMap;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeMap A Map of file extension to mime-type.
+ */
+ public void setMimeMap(Map mimeMap)
+ {
+ _mimeMap.clear();
+ if (mimeMap!=null)
+ {
+ for (Entry ext : mimeMap.entrySet())
+ _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the MIME type by filename extension.
+ * @param filename A file name
+ * @return MIME type matching the longest dot extension of the
+ * file name.
+ */
+ public String getMimeByExtension(String filename)
+ {
+ String type=null;
+
+ if (filename!=null)
+ {
+ int i=-1;
+ while(type==null)
+ {
+ i=filename.indexOf(".",i+1);
+
+ if (i<0 || i>=filename.length())
+ break;
+
+ String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+ if (_mimeMap!=null)
+ type=_mimeMap.get(ext);
+ if (type==null)
+ type=__dftMimeMap.get(ext);
+ }
+ }
+
+ if (type==null)
+ {
+ if (_mimeMap!=null)
+ type=_mimeMap.get("*");
+ if (type==null)
+ type=__dftMimeMap.get("*");
+ }
+
+ return type;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set a mime mapping
+ * @param extension
+ * @param type
+ */
+ public void addMimeMapping(String extension,String type)
+ {
+ _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Set getKnownMimeTypes()
+ {
+ return new HashSet<>(__dftMimeMap.values());
+ }
+
+ /* ------------------------------------------------------------ */
+ private static String normalizeMimeType(String type)
+ {
+ MimeTypes.Type t =CACHE.get(type);
+ if (t!=null)
+ return t.asString();
+
+ return StringUtil.asciiToLowerCase(type);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String getCharsetFromContentType(String value)
+ {
+ if (value==null)
+ return null;
+ int end=value.length();
+ int state=0;
+ int start=0;
+ boolean quote=false;
+ int i=0;
+ for (;i
+ * /foo/bar - an exact path specification.
+ * /foo/* - a prefix path specification (must end '/*').
+ * *.ext - a suffix path specification.
+ * / - the default path specification.
+ * "" - the / path specification
+ *
+ * Matching is performed in the following order
+ * Exact match.
+ * Longest prefix match.
+ * Longest suffix match.
+ * default.
+ *
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ *
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ *
+ * This class is not synchronized. If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap extends HashMap
+{
+ /* ------------------------------------------------------------ */
+ private static String __pathSpecSeparators = ":,";
+
+ /* ------------------------------------------------------------ */
+ /** Set the path spec separator.
+ * Multiple path specification may be included in a single string
+ * if they are separated by the characters set in this string.
+ * By default this class uses ":," characters as path separators.
+ * @param s separators
+ */
+ public static void setPathSpecSeparators(String s)
+ {
+ __pathSpecSeparators=s;
+ }
+
+ /* --------------------------------------------------------------- */
+ Trie> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map> _exactMap=new HashMap<>();
+
+ List> _defaultSingletonList=null;
+ MappedEntry _prefixDefault=null;
+ MappedEntry _default=null;
+ boolean _nodefault=false;
+
+ /* --------------------------------------------------------------- */
+ public PathMap()
+ {
+ this(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(boolean noDefault)
+ {
+ this(11, noDefault);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(int capacity)
+ {
+ this(capacity, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ private PathMap(int capacity, boolean noDefault)
+ {
+ super(capacity);
+ _nodefault=noDefault;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Construct from dictionary PathMap.
+ */
+ public PathMap(Map m)
+ {
+ putAll(m);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Add a single path match to the PathMap.
+ * @param pathSpec The path specification, or comma separated list of
+ * path specifications.
+ * @param object The object the path maps to
+ */
+ @Override
+ public O put(String pathSpec, O object)
+ {
+ if ("".equals(pathSpec.trim()))
+ {
+ MappedEntry entry = new MappedEntry<>("",object);
+ entry.setMapped("");
+ _exactMap.put("", entry);
+ return super.put("", object);
+ }
+
+ StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+ O old =null;
+
+ while (tok.hasMoreTokens())
+ {
+ String spec=tok.nextToken();
+
+ if (!spec.startsWith("/") && !spec.startsWith("*."))
+ throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+ old = super.put(spec,object);
+
+ // Make entry that was just created.
+ MappedEntry entry = new MappedEntry<>(spec,object);
+
+ if (entry.getKey().equals(spec))
+ {
+ if (spec.equals("/*"))
+ _prefixDefault=entry;
+ else if (spec.endsWith("/*"))
+ {
+ String mapped=spec.substring(0,spec.length()-2);
+ entry.setMapped(mapped);
+ while (!_prefixMap.put(mapped,entry))
+ _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_prefixMap,1.5);
+ }
+ else if (spec.startsWith("*."))
+ {
+ String suffix=spec.substring(2);
+ while(!_suffixMap.put(suffix,entry))
+ _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_suffixMap,1.5);
+ }
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ if (_nodefault)
+ _exactMap.put(spec,entry);
+ else
+ {
+ _default=entry;
+ _defaultSingletonList=Collections.singletonList(_default);
+ }
+ }
+ else
+ {
+ entry.setMapped(spec);
+ _exactMap.put(spec,entry);
+ }
+ }
+ }
+
+ return old;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get object matched by the path.
+ * @param path the path.
+ * @return Best matched object or null.
+ */
+ public O match(String path)
+ {
+ MappedEntry entry = getMatch(path);
+ if (entry!=null)
+ return entry.getValue();
+ return null;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Get the entry mapped by the best specification.
+ * @param path the path.
+ * @return Map.Entry of the best matched or null.
+ */
+ public MappedEntry getMatch(String path)
+ {
+ if (path==null)
+ return null;
+
+ int l=path.length();
+
+ MappedEntry entry=null;
+
+ //special case
+ if (l == 1 && path.charAt(0)=='/')
+ {
+ entry = _exactMap.get("");
+ if (entry != null)
+ return entry;
+ }
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ return entry;
+
+ // prefix search
+ int i=l;
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ return entry;
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ return _prefixDefault;
+
+ // Extension search
+ i=0;
+ final Trie> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ return entry;
+ }
+
+ // Default
+ return _default;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Get all entries matched by the path.
+ * Best match first.
+ * @param path Path to match
+ * @return List of Map.Entry instances key=pathSpec
+ */
+ public List extends Map.Entry> getMatches(String path)
+ {
+ MappedEntry entry;
+ List> entries=new ArrayList<>();
+
+ if (path==null)
+ return entries;
+ if (path.length()==0)
+ return _defaultSingletonList;
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ entries.add(entry);
+
+ // prefix search
+ int l=path.length();
+ int i=l;
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ entries.add(entry);
+
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ entries.add(_prefixDefault);
+
+ // Extension search
+ i=0;
+ final Trie> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // root match
+ if ("/".equals(path))
+ {
+ entry=_exactMap.get("");
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // Default
+ if (_default!=null)
+ entries.add(_default);
+
+ return entries;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return whether the path matches any entries in the PathMap,
+ * excluding the default entry
+ * @param path Path to match
+ * @return Whether the PathMap contains any entries that match this
+ */
+ public boolean containsMatch(String path)
+ {
+ MappedEntry> match = getMatch(path);
+ return match!=null && !match.equals(_default);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public O remove(Object pathSpec)
+ {
+ if (pathSpec!=null)
+ {
+ String spec=(String) pathSpec;
+ if (spec.equals("/*"))
+ _prefixDefault=null;
+ else if (spec.endsWith("/*"))
+ _prefixMap.remove(spec.substring(0,spec.length()-2));
+ else if (spec.startsWith("*."))
+ _suffixMap.remove(spec.substring(2));
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ _default=null;
+ _defaultSingletonList=null;
+ }
+ else
+ _exactMap.remove(spec);
+ }
+ return super.remove(pathSpec);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public void clear()
+ {
+ _exactMap.clear();
+ _prefixMap=new ArrayTernaryTrie<>(false);
+ _suffixMap=new ArrayTernaryTrie<>(false);
+ _default=null;
+ _defaultSingletonList=null;
+ _prefixDefault=null;
+ super.clear();
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path)
+ throws IllegalArgumentException
+ {
+ return match(pathSpec, path, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path, boolean noDefault)
+ throws IllegalArgumentException
+ {
+ if (pathSpec.length()==0)
+ return "/".equals(path);
+
+ char c = pathSpec.charAt(0);
+ if (c=='/')
+ {
+ if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+ return true;
+
+ if(isPathWildcardMatch(pathSpec, path))
+ return true;
+ }
+ else if (c=='*')
+ return path.regionMatches(path.length()-pathSpec.length()+1,
+ pathSpec,1,pathSpec.length()-1);
+ return false;
+ }
+
+ /* --------------------------------------------------------------- */
+ private static boolean isPathWildcardMatch(String pathSpec, String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl=pathSpec.length()-2;
+ if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+ {
+ if (path.length()==cpl || '/'==path.charAt(cpl))
+ return true;
+ }
+ return false;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that matches a path spec.
+ * @return null if no match at all.
+ */
+ public static String pathMatch(String pathSpec, String path)
+ {
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return path;
+
+ if (pathSpec.equals(path))
+ return path;
+
+ if (isPathWildcardMatch(pathSpec, path))
+ return path.substring(0,pathSpec.length()-2);
+ }
+ else if (c=='*')
+ {
+ if (path.regionMatches(path.length()-(pathSpec.length()-1),
+ pathSpec,1,pathSpec.length()-1))
+ return path;
+ }
+ return null;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that is after a path spec.
+ * @return The path info string
+ */
+ public static String pathInfo(String pathSpec, String path)
+ {
+ if ("".equals(pathSpec))
+ return path; //servlet 3 spec sec 12.2 will be '/'
+
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return null;
+
+ boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+ // handle the case where pathSpec uses a wildcard and path info is "/*"
+ if (pathSpec.equals(path) && !wildcard)
+ return null;
+
+ if (wildcard)
+ {
+ if (path.length()==pathSpec.length()-2)
+ return null;
+ return path.substring(pathSpec.length()-2);
+ }
+ }
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Relative path.
+ * @param base The base the path is relative to.
+ * @param pathSpec The spec of the path segment to ignore.
+ * @param path the additional path
+ * @return base plus path with pathspec removed
+ */
+ public static String relativePath(String base,
+ String pathSpec,
+ String path )
+ {
+ String info=pathInfo(pathSpec,path);
+ if (info==null)
+ info=path;
+
+ if( info.startsWith( "./"))
+ info = info.substring( 2);
+ if( base.endsWith( URIUtil.SLASH))
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info.substring(1);
+ else
+ path = base + info;
+ else
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info;
+ else
+ path = base + URIUtil.SLASH + info;
+ return path;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class MappedEntry implements Map.Entry
+ {
+ private final String key;
+ private final O value;
+ private String mapped;
+
+ MappedEntry(String key, O value)
+ {
+ this.key=key;
+ this.value=value;
+ }
+
+ @Override
+ public String getKey()
+ {
+ return key;
+ }
+
+ @Override
+ public O getValue()
+ {
+ return value;
+ }
+
+ @Override
+ public O setValue(O o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString()
+ {
+ return key+"="+value;
+ }
+
+ public String getMapped()
+ {
+ return mapped;
+ }
+
+ void setMapped(String mapped)
+ {
+ this.mapped = mapped;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/encoding.properties b/lib/jetty/org/eclipse/jetty/http/encoding.properties
new file mode 100644
index 00000000..311c8021
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/encoding.properties
@@ -0,0 +1,4 @@
+text/html = ISO-8859-1
+text/plain = ISO-8859-1
+text/xml = UTF-8
+text/json = UTF-8
diff --git a/lib/jetty/org/eclipse/jetty/http/mime.properties b/lib/jetty/org/eclipse/jetty/http/mime.properties
new file mode 100644
index 00000000..b2709897
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/mime.properties
@@ -0,0 +1,183 @@
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+cab=application/x-cabinet
+cdf=application/x-netcdf
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/comma-separated-values
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+nc=application/x-netcdf
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+src=application/x-wais-source
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svg=image/svg+xml
+swf=application/x-shockwave-flash
+t=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
diff --git a/lib/jetty/org/eclipse/jetty/http/package-info.java b/lib/jetty/org/eclipse/jetty/http/package-info.java
new file mode 100644
index 00000000..825422a3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
diff --git a/lib/jetty/org/eclipse/jetty/http/useragents b/lib/jetty/org/eclipse/jetty/http/useragents
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644
index 00000000..7f8e9f81
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
@@ -0,0 +1,587 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/**
+ * A convenience base implementation of {@link Connection}.
+ * This class uses the capabilities of the {@link EndPoint} API to provide a
+ * more traditional style of async reading. A call to {@link #fillInterested()}
+ * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * as appropriate.
+ */
+public abstract class AbstractConnection implements Connection
+{
+ private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+
+ public static final boolean EXECUTE_ONFILLABLE=true;
+
+ private final List listeners = new CopyOnWriteArrayList<>();
+ private final AtomicReference _state = new AtomicReference<>(IDLE);
+ private final long _created=System.currentTimeMillis();
+ private final EndPoint _endPoint;
+ private final Executor _executor;
+ private final Callback _readCallback;
+ private final boolean _executeOnfillable;
+ private int _inputBufferSize=2048;
+
+ protected AbstractConnection(EndPoint endp, Executor executor)
+ {
+ this(endp,executor,EXECUTE_ONFILLABLE);
+ }
+
+ protected AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
+ {
+ if (executor == null)
+ throw new IllegalArgumentException("Executor must not be null!");
+ _endPoint = endp;
+ _executor = executor;
+ _readCallback = new ReadCallback();
+ _executeOnfillable=executeOnfillable;
+ _state.set(IDLE);
+ }
+
+ @Override
+ public void addListener(Listener listener)
+ {
+ listeners.add(listener);
+ }
+
+ public int getInputBufferSize()
+ {
+ return _inputBufferSize;
+ }
+
+ public void setInputBufferSize(int inputBufferSize)
+ {
+ _inputBufferSize = inputBufferSize;
+ }
+
+ protected Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ protected void failedCallback(final Callback callback, final Throwable x)
+ {
+ if (NonBlockingThread.isNonBlockingThread())
+ {
+ try
+ {
+ getExecutor().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ callback.failed(x);
+ }
+ });
+ }
+ catch(RejectedExecutionException e)
+ {
+ LOG.debug(e);
+ callback.failed(x);
+ }
+ }
+ else
+ {
+ callback.failed(x);
+ }
+ }
+
+ /**
+ * Utility method to be called to register read interest.
+ * After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * will be called back as appropriate.
+ * @see #onFillable()
+ */
+ public void fillInterested()
+ {
+ LOG.debug("fillInterested {}",this);
+
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.fillInterested()))
+ break;
+ }
+ }
+
+ public void fillInterested(Callback callback)
+ {
+ LOG.debug("fillInterested {}",this);
+
+ while(true)
+ {
+ State state=_state.get();
+ // TODO yuck
+ if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+ break;
+ State next=new FillingInterestedCallback(callback,state);
+ if (next(state,next))
+ break;
+ }
+ }
+
+ /**
+ * Callback method invoked when the endpoint is ready to be read.
+ * @see #fillInterested()
+ */
+ public abstract void onFillable();
+
+ /**
+ * Callback method invoked when the endpoint failed to be ready to be read.
+ * @param cause the exception that caused the failure
+ */
+ protected void onFillInterestedFailed(Throwable cause)
+ {
+ LOG.debug("{} onFillInterestedFailed {}", this, cause);
+ if (_endPoint.isOpen())
+ {
+ boolean close = true;
+ if (cause instanceof TimeoutException)
+ close = onReadTimeout();
+ if (close)
+ {
+ if (_endPoint.isOutputShutdown())
+ _endPoint.close();
+ else
+ _endPoint.shutdownOutput();
+ }
+ }
+
+ if (_endPoint.isOpen())
+ fillInterested();
+ }
+
+ /**
+ * Callback method invoked when the endpoint failed to be ready to be read after a timeout
+ * @return true to signal that the endpoint must be closed, false to keep the endpoint open
+ */
+ protected boolean onReadTimeout()
+ {
+ return true;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ LOG.debug("onOpen {}", this);
+
+ for (Listener listener : listeners)
+ listener.onOpened(this);
+ }
+
+ @Override
+ public void onClose()
+ {
+ LOG.debug("onClose {}",this);
+
+ for (Listener listener : listeners)
+ listener.onClosed(this);
+ }
+
+ @Override
+ public EndPoint getEndPoint()
+ {
+ return _endPoint;
+ }
+
+ @Override
+ public void close()
+ {
+ getEndPoint().close();
+ }
+
+ @Override
+ public int getMessagesIn()
+ {
+ return -1;
+ }
+
+ @Override
+ public int getMessagesOut()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getBytesIn()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getBytesOut()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getCreatedTimeStamp()
+ {
+ return _created;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
+ }
+
+ public boolean next(State state, State next)
+ {
+ if (next==null)
+ return true;
+ if(_state.compareAndSet(state,next))
+ {
+ LOG.debug("{}-->{} {}",state,next,this);
+ if (next!=state)
+ next.onEnter(AbstractConnection.this);
+ return true;
+ }
+ return false;
+ }
+
+ private static final class IdleState extends State
+ {
+ private IdleState()
+ {
+ super("IDLE");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillInterestedState extends State
+ {
+ private FillInterestedState()
+ {
+ super("FILL_INTERESTED");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ connection.getEndPoint().fillInterested(connection._readCallback);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ @Override
+ public State onFillable()
+ {
+ return FILLING;
+ }
+
+ @Override
+ State onFailed()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class RefillingState extends State
+ {
+ private RefillingState()
+ {
+ super("REFILLING");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class FillingFillInterestedState extends State
+ {
+ private FillingFillInterestedState(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ State onFilled()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillingState extends State
+ {
+ private FillingState()
+ {
+ super("FILLING");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ if (connection._executeOnfillable)
+ connection.getExecutor().execute(connection._runOnFillable);
+ else
+ connection._runOnFillable.run();
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ public static class State
+ {
+ private final String _name;
+ State(String name)
+ {
+ _name=name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+
+ void onEnter(AbstractConnection connection)
+ {
+ }
+
+ State fillInterested()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFillable()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFilled()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFailed()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+ }
+
+
+ public static final State IDLE=new IdleState();
+
+ public static final State FILL_INTERESTED=new FillInterestedState();
+
+ public static final State FILLING=new FillingState();
+
+ public static final State REFILLING=new RefillingState();
+
+ public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+
+ public class NestedState extends State
+ {
+ private final State _nested;
+
+ NestedState(State nested)
+ {
+ super("NESTED("+nested+")");
+ _nested=nested;
+ }
+ NestedState(String name,State nested)
+ {
+ super(name+"("+nested+")");
+ _nested=nested;
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return new NestedState(_nested.fillInterested());
+ }
+
+ @Override
+ State onFillable()
+ {
+ return new NestedState(_nested.onFillable());
+ }
+
+ @Override
+ State onFilled()
+ {
+ return new NestedState(_nested.onFilled());
+ }
+ }
+
+
+ public class FillingInterestedCallback extends NestedState
+ {
+ private final Callback _callback;
+
+ FillingInterestedCallback(Callback callback,State nested)
+ {
+ super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+ _callback=callback;
+ }
+
+ @Override
+ void onEnter(final AbstractConnection connection)
+ {
+ Callback callback=new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.failed(x);
+ }
+ };
+
+ connection.getEndPoint().fillInterested(callback);
+ }
+ }
+
+ private final Runnable _runOnFillable = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ onFillable();
+ }
+ finally
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFilled()))
+ break;
+ }
+ }
+ }
+ };
+
+
+ private class ReadCallback implements Callback
+ {
+ @Override
+ public void succeeded()
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFillable()))
+ break;
+ }
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ _executor.execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFailed()))
+ break;
+ }
+ onFillInterestedFailed(x);
+ }
+ });
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
+ }
+ };
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
new file mode 100644
index 00000000..8fa2cc86
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -0,0 +1,180 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
+{
+ private static final Logger LOG = Log.getLogger(AbstractEndPoint.class);
+ private final long _created=System.currentTimeMillis();
+ private final InetSocketAddress _local;
+ private final InetSocketAddress _remote;
+ private volatile Connection _connection;
+
+ private final FillInterest _fillInterest = new FillInterest()
+ {
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ return AbstractEndPoint.this.needsFill();
+ }
+ };
+
+ private final WriteFlusher _writeFlusher = new WriteFlusher(this)
+ {
+ @Override
+ protected void onIncompleteFlushed()
+ {
+ AbstractEndPoint.this.onIncompleteFlush();
+ }
+ };
+
+ protected AbstractEndPoint(Scheduler scheduler,InetSocketAddress local,InetSocketAddress remote)
+ {
+ super(scheduler);
+ _local=local;
+ _remote=remote;
+ }
+
+ @Override
+ public long getCreatedTimeStamp()
+ {
+ return _created;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ return _local;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ return _remote;
+ }
+
+ @Override
+ public Connection getConnection()
+ {
+ return _connection;
+ }
+
+ @Override
+ public void setConnection(Connection connection)
+ {
+ _connection = connection;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ LOG.debug("onOpen {}",this);
+ super.onOpen();
+ }
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ LOG.debug("onClose {}",this);
+ _writeFlusher.onClose();
+ _fillInterest.onClose();
+ }
+
+ @Override
+ public void close()
+ {
+ onClose();
+ }
+
+ @Override
+ public void fillInterested(Callback callback) throws IllegalStateException
+ {
+ notIdle();
+ _fillInterest.register(callback);
+ }
+
+ @Override
+ public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+ {
+ _writeFlusher.write(callback, buffers);
+ }
+
+ protected abstract void onIncompleteFlush();
+
+ protected abstract boolean needsFill() throws IOException;
+
+ protected FillInterest getFillInterest()
+ {
+ return _fillInterest;
+ }
+
+ protected WriteFlusher getWriteFlusher()
+ {
+ return _writeFlusher;
+ }
+
+ @Override
+ protected void onIdleExpired(TimeoutException timeout)
+ {
+ boolean output_shutdown=isOutputShutdown();
+ boolean input_shutdown=isInputShutdown();
+ boolean fillFailed = _fillInterest.onFail(timeout);
+ boolean writeFailed = _writeFlusher.onFail(timeout);
+
+ // If the endpoint is half closed and there was no onFail handling, the close here
+ // This handles the situation where the connection has completed its close handling
+ // and the endpoint is half closed, but the other party does not complete the close.
+ // This perhaps should not check for half closed, however the servlet spec case allows
+ // for a dispatched servlet or suspended request to extend beyond the connections idle
+ // time. So if this test would always close an idle endpoint that is not handled, then
+ // we would need a mode to ignore timeouts for some HTTP states
+ if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+ close();
+ else
+ LOG.debug("Ignored idle endpoint {}",this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d,%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ getRemoteAddress(),
+ getLocalAddress().getPort(),
+ isOpen()?"Open":"CLOSED",
+ isInputShutdown()?"ISHUT":"in",
+ isOutputShutdown()?"OSHUT":"out",
+ _fillInterest.isInterested()?"R":"-",
+ _writeFlusher.isInProgress()?"W":"-",
+ getIdleTimeout(),
+ getConnection()==null?null:getConnection().getClass().getSimpleName());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
new file mode 100644
index 00000000..fa3a01d0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
@@ -0,0 +1,133 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ArrayByteBufferPool implements ByteBufferPool
+{
+ private final int _min;
+ private final Bucket[] _direct;
+ private final Bucket[] _indirect;
+ private final int _inc;
+
+ public ArrayByteBufferPool()
+ {
+ this(0,1024,64*1024);
+ }
+
+ public ArrayByteBufferPool(int minSize, int increment, int maxSize)
+ {
+ if (minSize>=increment)
+ throw new IllegalArgumentException("minSize >= increment");
+ if ((maxSize%increment)!=0 || increment>=maxSize)
+ throw new IllegalArgumentException("increment must be a divisor of maxSize");
+ _min=minSize;
+ _inc=increment;
+
+ _direct=new Bucket[maxSize/increment];
+ _indirect=new Bucket[maxSize/increment];
+
+ int size=0;
+ for (int i=0;i<_direct.length;i++)
+ {
+ size+=_inc;
+ _direct[i]=new Bucket(size);
+ _indirect[i]=new Bucket(size);
+ }
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ Bucket bucket = bucketFor(size,direct);
+ ByteBuffer buffer = bucket==null?null:bucket._queue.poll();
+
+ if (buffer == null)
+ {
+ int capacity = bucket==null?size:bucket._size;
+ buffer = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+ }
+
+ return buffer;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer!=null)
+ {
+ Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
+ if (bucket!=null)
+ {
+ BufferUtil.clear(buffer);
+ bucket._queue.offer(buffer);
+ }
+ }
+ }
+
+ public void clear()
+ {
+ for (int i=0;i<_direct.length;i++)
+ {
+ _direct[i]._queue.clear();
+ _indirect[i]._queue.clear();
+ }
+ }
+
+ private Bucket bucketFor(int size,boolean direct)
+ {
+ if (size<=_min)
+ return null;
+ int b=(size-1)/_inc;
+ if (b>=_direct.length)
+ return null;
+ Bucket bucket = direct?_direct[b]:_indirect[b];
+
+ return bucket;
+ }
+
+ public static class Bucket
+ {
+ public final int _size;
+ public final Queue _queue= new ConcurrentLinkedQueue<>();
+
+ Bucket(int size)
+ {
+ _size=size;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("Bucket@%x{%d,%d}",hashCode(),_size,_queue.size());
+ }
+ }
+
+
+ // Package local for testing
+ Bucket[] bucketsFor(boolean direct)
+ {
+ return direct ? _direct : _indirect;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
new file mode 100644
index 00000000..4b8c527c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
@@ -0,0 +1,409 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** ByteArrayEndPoint.
+ *
+ */
+public class ByteArrayEndPoint extends AbstractEndPoint
+{
+ static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
+ public final static InetSocketAddress NOIP=new InetSocketAddress(0);
+
+ protected volatile ByteBuffer _in;
+ protected volatile ByteBuffer _out;
+ protected volatile boolean _ishut;
+ protected volatile boolean _oshut;
+ protected volatile boolean _closed;
+ protected volatile boolean _growOutput;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint()
+ {
+ this(null,0,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint(byte[] input, int outputSize)
+ {
+ this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint(String input, int outputSize)
+ {
+ this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler scheduler, long idleTimeoutMs)
+ {
+ this(scheduler,idleTimeoutMs,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, byte[] input, int outputSize)
+ {
+ this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, String input, int outputSize)
+ {
+ this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, ByteBuffer input, ByteBuffer output)
+ {
+ super(timer,NOIP,NOIP);
+ _in=input==null?BufferUtil.EMPTY_BUFFER:input;
+ _out=output==null?BufferUtil.allocate(1024):output;
+ setIdleTimeout(idleTimeoutMs);
+ }
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void onIncompleteFlush()
+ {
+ // Don't need to do anything here as takeOutput does the signalling.
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ if (_closed)
+ throw new ClosedChannelException();
+ return _in == null || BufferUtil.hasContent(_in);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the in.
+ */
+ public ByteBuffer getIn()
+ {
+ return _in;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void setInputEOF()
+ {
+ _in = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param in The in to set.
+ */
+ public void setInput(ByteBuffer in)
+ {
+ _in = in;
+ if (in == null || BufferUtil.hasContent(in))
+ getFillInterest().fillable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setInput(String s)
+ {
+ setInput(BufferUtil.toBuffer(s,StandardCharsets.UTF_8));
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setInput(String s,Charset charset)
+ {
+ setInput(BufferUtil.toBuffer(s,charset));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public ByteBuffer getOutput()
+ {
+ return _out;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String getOutputString()
+ {
+ return getOutputString(StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String getOutputString(Charset charset)
+ {
+ return BufferUtil.toString(_out,charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public ByteBuffer takeOutput()
+ {
+ ByteBuffer b=_out;
+ _out=BufferUtil.allocate(b.capacity());
+ getWriteFlusher().completeWrite();
+ return b;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String takeOutputString()
+ {
+ return takeOutputString(StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String takeOutputString(Charset charset)
+ {
+ ByteBuffer buffer=takeOutput();
+ return BufferUtil.toString(buffer,charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param out The out to set.
+ */
+ public void setOutput(ByteBuffer out)
+ {
+ _out = out;
+ getWriteFlusher().completeWrite();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#isOpen()
+ */
+ @Override
+ public boolean isOpen()
+ {
+ return !_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _ishut||_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _oshut||_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void shutdownInput()
+ {
+ _ishut=true;
+ if (_oshut)
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#shutdownOutput()
+ */
+ @Override
+ public void shutdownOutput()
+ {
+ _oshut=true;
+ if (_ishut)
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#close()
+ */
+ @Override
+ public void close()
+ {
+ super.close();
+ _closed=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true
if there are bytes remaining to be read from the encoded input
+ */
+ public boolean hasMore()
+ {
+ return getOutput().position()>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+ */
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (_closed)
+ throw new EofException("CLOSED");
+ if (_in==null)
+ shutdownInput();
+ if (_ishut)
+ return -1;
+ int filled=BufferUtil.append(buffer,_in);
+ if (filled>0)
+ notIdle();
+ return filled;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+ */
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ if (_closed)
+ throw new IOException("CLOSED");
+ if (_oshut)
+ throw new IOException("OSHUT");
+
+ boolean flushed=true;
+ boolean idle=true;
+
+ for (ByteBuffer b : buffers)
+ {
+ if (BufferUtil.hasContent(b))
+ {
+ if (_growOutput && b.remaining()>BufferUtil.space(_out))
+ {
+ BufferUtil.compact(_out);
+ if (b.remaining()>BufferUtil.space(_out))
+ {
+ ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
+ BufferUtil.append(n,_out);
+ _out=n;
+ }
+ }
+
+ if (BufferUtil.append(_out,b)>0)
+ idle=false;
+
+ if (BufferUtil.hasContent(b))
+ {
+ flushed=false;
+ break;
+ }
+ }
+ }
+ if (!idle)
+ notIdle();
+ return flushed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public void reset()
+ {
+ getFillInterest().onClose();
+ getWriteFlusher().onClose();
+ _ishut=false;
+ _oshut=false;
+ _closed=false;
+ _in=null;
+ BufferUtil.clear(_out);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getConnection()
+ */
+ @Override
+ public Object getTransport()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the growOutput
+ */
+ public boolean isGrowOutput()
+ {
+ return _growOutput;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param growOutput the growOutput to set
+ */
+ public void setGrowOutput(boolean growOutput)
+ {
+ _growOutput=growOutput;
+ }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
new file mode 100644
index 00000000..302adde3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link ByteBuffer} pool.
+ * Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
+ * if they are released, they may be recycled and reused, otherwise they will be garbage
+ * collected as usual.
+ */
+public interface ByteBufferPool
+{
+ /**
+ * Requests a {@link ByteBuffer} of the given size.
+ * The returned buffer may have a bigger capacity than the size being
+ * requested but it will have the limit set to the given size.
+ *
+ * @param size the size of the buffer
+ * @param direct whether the buffer must be direct or not
+ * @return the requested buffer
+ * @see #release(ByteBuffer)
+ */
+ public ByteBuffer acquire(int size, boolean direct);
+
+ /**
+ * Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+ * (but not necessarily), making it available for recycling and reuse.
+ *
+ * @param buffer the buffer to return
+ * @see #acquire(int, boolean)
+ */
+ public void release(ByteBuffer buffer);
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
new file mode 100644
index 00000000..c65ca0cc
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
@@ -0,0 +1,229 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Channel End Point.
+ * Holds the channel and socket for an NIO endpoint.
+ */
+public class ChannelEndPoint extends AbstractEndPoint
+{
+ private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+ private final ByteChannel _channel;
+ private final Socket _socket;
+ private volatile boolean _ishut;
+ private volatile boolean _oshut;
+
+ public ChannelEndPoint(Scheduler scheduler,SocketChannel channel)
+ {
+ super(scheduler,
+ (InetSocketAddress)channel.socket().getLocalSocketAddress(),
+ (InetSocketAddress)channel.socket().getRemoteSocketAddress());
+ _channel = channel;
+ _socket=channel.socket();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return _channel.isOpen();
+ }
+
+ protected void shutdownInput()
+ {
+ LOG.debug("ishut {}", this);
+ _ishut=true;
+ if (_oshut)
+ close();
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ LOG.debug("oshut {}", this);
+ _oshut = true;
+ if (_channel.isOpen())
+ {
+ try
+ {
+ if (!_socket.isOutputShutdown())
+ _socket.shutdownOutput();
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ if (_ishut)
+ {
+ close();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _oshut || !_channel.isOpen() || _socket.isOutputShutdown();
+ }
+
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _ishut || !_channel.isOpen() || _socket.isInputShutdown();
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ LOG.debug("close {}", this);
+ try
+ {
+ _channel.close();
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ _ishut=true;
+ _oshut=true;
+ }
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (_ishut)
+ return -1;
+
+ int pos=BufferUtil.flipToFill(buffer);
+ try
+ {
+ int filled = _channel.read(buffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("filled {} {}", filled, this);
+
+ if (filled>0)
+ notIdle();
+ else if (filled==-1)
+ shutdownInput();
+
+ return filled;
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ shutdownInput();
+ return -1;
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(buffer,pos);
+ }
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ int flushed=0;
+ try
+ {
+ if (buffers.length==1)
+ flushed=_channel.write(buffers[0]);
+ else if (buffers.length>1 && _channel instanceof GatheringByteChannel)
+ flushed= (int)((GatheringByteChannel)_channel).write(buffers,0,buffers.length);
+ else
+ {
+ for (ByteBuffer b : buffers)
+ {
+ if (b.hasRemaining())
+ {
+ int l=_channel.write(b);
+ if (l>0)
+ flushed+=l;
+ if (b.hasRemaining())
+ break;
+ }
+ }
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("flushed {} {}", flushed, this);
+ }
+ catch (IOException e)
+ {
+ throw new EofException(e);
+ }
+
+ if (flushed>0)
+ notIdle();
+
+ for (ByteBuffer b : buffers)
+ if (!BufferUtil.isEmpty(b))
+ return false;
+
+ return true;
+ }
+
+ public ByteChannel getChannel()
+ {
+ return _channel;
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return _channel;
+ }
+
+ public Socket getSocket()
+ {
+ return _socket;
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644
index 00000000..7eb59318
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+ /**
+ *
+ * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+ * @param context the context data to create the connection
+ * @return a new {@link Connection}
+ * @throws IOException if the connection cannot be created
+ */
+ public Connection newConnection(EndPoint endPoint, Map context) throws IOException;
+
+ public static class Helper
+ {
+ private static Logger LOG = Log.getLogger(Helper.class);
+
+ private Helper()
+ {
+ }
+
+ /**
+ * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+ * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+ *
+ * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+ * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
+ * @param oldConnection the old connection to replace
+ * @param newConnection the new connection replacement
+ */
+ public static void replaceConnection(Connection oldConnection, Connection newConnection)
+ {
+ close(oldConnection);
+ oldConnection.getEndPoint().setConnection(newConnection);
+ open(newConnection);
+ }
+
+ private static void open(Connection connection)
+ {
+ try
+ {
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+
+ private static void close(Connection connection)
+ {
+ try
+ {
+ connection.onClose();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/Connection.java b/lib/jetty/org/eclipse/jetty/io/Connection.java
new file mode 100644
index 00000000..96baa016
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/Connection.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * A {@link Connection} is associated to an {@link EndPoint} so that I/O events
+ * happening on the {@link EndPoint} can be processed by the {@link Connection}.
+ * A typical implementation of {@link Connection} overrides {@link #onOpen()} to
+ * {@link EndPoint#fillInterested(Callback) set read interest} on the {@link EndPoint},
+ * and when the {@link EndPoint} signals read readyness, this {@link Connection} can
+ * read bytes from the network and interpret them.
+ */
+public interface Connection extends Closeable
+{
+ public void addListener(Listener listener);
+
+ /**
+ * Callback method invoked when this {@link Connection} is opened.
+ * Creators of the connection implementation are responsible for calling this method.
+ */
+ public void onOpen();
+
+ /**
+ * Callback method invoked when this {@link Connection} is closed.
+ * Creators of the connection implementation are responsible for calling this method.
+ */
+ public void onClose();
+
+ /**
+ * @return the {@link EndPoint} associated with this {@link Connection}
+ */
+ public EndPoint getEndPoint();
+
+ /**
+ * Performs a logical close of this connection.
+ * For simple connections, this may just mean to delegate the close to the associated
+ * {@link EndPoint} but, for example, SSL connections should write the SSL close message
+ * before closing the associated {@link EndPoint}.
+ */
+ @Override
+ public void close();
+
+ public int getMessagesIn();
+ public int getMessagesOut();
+ public long getBytesIn();
+ public long getBytesOut();
+ public long getCreatedTimeStamp();
+
+
+ public interface Listener
+ {
+ public void onOpened(Connection connection);
+
+ public void onClosed(Connection connection);
+
+ public static class Adapter implements Listener
+ {
+ @Override
+ public void onOpened(Connection connection)
+ {
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EndPoint.java b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
new file mode 100644
index 00000000..87adb40b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
@@ -0,0 +1,243 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+
+
+/**
+ *
+ * A transport EndPoint
+ *
+ * Asynchronous Methods
+ * The asynchronous scheduling methods of {@link EndPoint}
+ * has been influenced by NIO.2 Futures and Completion
+ * handlers, but does not use those actual interfaces because they have
+ * some inefficiencies.
+ * This class will frequently be used in conjunction with some of the utility
+ * implementations of {@link Callback}, such as {@link FutureCallback} and
+ * {@link ExecutorCallback}. Examples are:
+ *
+ * Blocking Read
+ * A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ *
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ *
+ *
+ * Dispatched Read
+ * By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ *
+ * endpoint.fillInterested("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ * public void onCompleted(String context)
+ * {
+ * int filled=endpoint.fill(mybuffer);
+ * ...
+ * }
+ * public void onFailed(String context,Throwable cause) {...}
+ * });
+ *
+ * The executor callback can also be customized to not dispatch in some circumstances when
+ * it knows it can use the callback thread and does not need to dispatch.
+ *
+ * Blocking Write
+ * The write contract is that the callback complete is not called until all data has been
+ * written or there is a failure. For blocking this looks like:
+ *
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
+ * String context = future.get(); // This blocks
+ *
+ *
+ * Dispatched Write
+ * Note also that multiple buffers may be passed in write so that gather writes
+ * can be done:
+ *
+ * endpoint.write("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ * public void onCompleted(String context)
+ * {
+ * int filled=endpoint.fill(mybuffer);
+ * ...
+ * }
+ * public void onFailed(String context,Throwable cause) {...}
+ * },headerBuffer,contentBuffer);
+ *
+ */
+public interface EndPoint extends Closeable
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The local Inet address to which this EndPoint
is bound, or null
+ * if this EndPoint
does not represent a network connection.
+ */
+ InetSocketAddress getLocalAddress();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The remote Inet address to which this EndPoint
is bound, or null
+ * if this EndPoint
does not represent a network connection.
+ */
+ InetSocketAddress getRemoteAddress();
+
+ /* ------------------------------------------------------------ */
+ boolean isOpen();
+
+ /* ------------------------------------------------------------ */
+ long getCreatedTimeStamp();
+
+ /* ------------------------------------------------------------ */
+ /** Shutdown the output.
+ * This call indicates that no more data will be sent on this endpoint that
+ * that the remote end should read an EOF once all previously sent data has been
+ * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
+ * TLS close handshake) or both.
+ *
+ * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
+ * as {@link #close()}.
+ */
+ void shutdownOutput();
+
+ /* ------------------------------------------------------------ */
+ /** Test if output is shutdown.
+ * The output is shutdown by a call to {@link #shutdownOutput()}
+ * or {@link #close()}.
+ * @return true if the output is shutdown or the endpoint is closed.
+ */
+ boolean isOutputShutdown();
+
+ /* ------------------------------------------------------------ */
+ /** Test if the input is shutdown.
+ * The input is shutdown if an EOF has been read while doing
+ * a {@link #fill(ByteBuffer)}. Once the input is shutdown, all calls to
+ * {@link #fill(ByteBuffer)} will return -1, until such time as the
+ * end point is close, when they will return {@link EofException}.
+ * @return True if the input is shutdown or the endpoint is closed.
+ */
+ boolean isInputShutdown();
+
+ /**
+ * Close any backing stream associated with the endpoint
+ */
+ @Override
+ void close();
+
+ /**
+ * Fill the passed buffer with data from this endpoint. The bytes are appended to any
+ * data already in the buffer by writing from the buffers limit up to it's capacity.
+ * The limit is updated to include the filled bytes.
+ *
+ * @param buffer The buffer to fill. The position and limit are modified during the fill. After the
+ * operation, the position is unchanged and the limit is increased to reflect the new data filled.
+ * @return an int
value indicating the number of bytes
+ * filled or -1 if EOF is read or the input is shutdown.
+ * @throws EofException If the endpoint is closed.
+ */
+ int fill(ByteBuffer buffer) throws IOException;
+
+
+ /**
+ * Flush data from the passed header/buffer to this endpoint. As many bytes as can be consumed
+ * are taken from the header/buffer position up until the buffer limit. The header/buffers position
+ * is updated to indicate how many bytes have been consumed.
+ * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its
+ * destination (ie is not buffering any data).
+ *
+ * @throws EofException If the endpoint is closed or output is shutdown.
+ */
+ boolean flush(ByteBuffer... buffer) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The underlying transport object (socket, channel, etc.)
+ */
+ Object getTransport();
+
+ /* ------------------------------------------------------------ */
+ /** Get the max idle time in ms.
+ *
The max idle time is the time the endpoint can be idle before
+ * extraordinary handling takes place.
+ * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+ */
+ long getIdleTimeout();
+
+ /* ------------------------------------------------------------ */
+ /** Set the idle timeout.
+ * @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
+ */
+ void setIdleTimeout(long idleTimeout);
+
+
+ /**
+ *
Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.
+ *
+ * @param callback the callback to call when an error occurs or we are readable.
+ * @throws ReadPendingException if another read operation is concurrent.
+ */
+ void fillInterested(Callback callback) throws ReadPendingException;
+
+ /**
+ * Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+ * all the data has been flushed or an error occurs.
+ *
+ * @param callback the callback to call when an error occurs or the write completed.
+ * @param buffers one or more {@link ByteBuffer}s that will be flushed.
+ * @throws WritePendingException if another write operation is concurrent.
+ */
+ void write(Callback callback, ByteBuffer... buffers) throws WritePendingException;
+
+ /**
+ * @return the {@link Connection} associated with this {@link EndPoint}
+ * @see #setConnection(Connection)
+ */
+ Connection getConnection();
+
+ /**
+ * @param connection the {@link Connection} associated with this {@link EndPoint}
+ * @see #getConnection()
+ */
+ void setConnection(Connection connection);
+
+ /**
+ * Callback method invoked when this {@link EndPoint} is opened.
+ * @see #onClose()
+ */
+ void onOpen();
+
+ /**
+ * Callback method invoked when this {@link EndPoint} is close.
+ * @see #onOpen()
+ */
+ void onClose();
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EofException.java b/lib/jetty/org/eclipse/jetty/io/EofException.java
new file mode 100644
index 00000000..72042f40
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/EofException.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * This is thrown by Jetty to distinguish between EOF received from
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+public class EofException extends EOFException
+{
+ public EofException()
+ {
+ }
+
+ public EofException(String reason)
+ {
+ super(reason);
+ }
+
+ public EofException(Throwable th)
+ {
+ if (th!=null)
+ initCause(th);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/FillInterest.java b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
new file mode 100644
index 00000000..b2c3f685
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A Utility class to help implement {@link EndPoint#fillInterested(Callback)}
+ * by keeping state and calling the context and callback objects.
+ *
+ */
+public abstract class FillInterest
+{
+ private final static Logger LOG = Log.getLogger(FillInterest.class);
+ private final AtomicReference _interested = new AtomicReference<>(null);
+
+ /* ------------------------------------------------------------ */
+ protected FillInterest()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to register interest in a callback when a read is possible.
+ * The callback will be called either immediately if {@link #needsFill()}
+ * returns true or eventually once {@link #fillable()} is called.
+ * @param callback
+ * @throws ReadPendingException
+ */
+ public void register(Callback callback) throws ReadPendingException
+ {
+ if (callback==null)
+ throw new IllegalArgumentException();
+
+ if (!_interested.compareAndSet(null,callback))
+ {
+ LOG.warn("Read pending for "+_interested.get()+" pervented "+callback);
+ throw new ReadPendingException();
+ }
+ try
+ {
+ if (needsFill())
+ fillable();
+ }
+ catch(IOException e)
+ {
+ onFail(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to signal that a read is now possible.
+ */
+ public void fillable()
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ callback.succeeded();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if a read callback has been registered
+ */
+ public boolean isInterested()
+ {
+ return _interested.get()!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to signal a failure to a registered interest
+ * @return true if the cause was passed to a {@link Callback} instance
+ */
+ public boolean onFail(Throwable cause)
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ {
+ callback.failed(cause);
+ return true;
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onClose()
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ callback.failed(new ClosedChannelException());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("FillInterest@%x{%b,%s}",hashCode(),_interested.get(),_interested.get());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Register the read interest
+ * Abstract method to be implemented by the Specific ReadInterest to
+ * enquire if a read is immediately possible and if not to schedule a future
+ * call to {@link #fillable()} or {@link #onFail(Throwable)}
+ * @return true if a read is possible
+ * @throws IOException
+ */
+ abstract protected boolean needsFill() throws IOException;
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
new file mode 100644
index 00000000..8b251ac8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
@@ -0,0 +1,182 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ *
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place. If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+ private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+ private final Scheduler _scheduler;
+ private final AtomicReference _timeout = new AtomicReference<>();
+ private volatile long _idleTimeout;
+ private volatile long _idleTimestamp = System.currentTimeMillis();
+
+ private final Runnable _idleTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ long idleLeft = checkIdleTimeout();
+ if (idleLeft >= 0)
+ scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+ }
+ };
+
+ /**
+ * @param scheduler A scheduler used to schedule checks for the idle timeout.
+ */
+ public IdleTimeout(Scheduler scheduler)
+ {
+ _scheduler = scheduler;
+ }
+
+ public long getIdleTimestamp()
+ {
+ return _idleTimestamp;
+ }
+
+ public long getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ public void setIdleTimeout(long idleTimeout)
+ {
+ long old = _idleTimeout;
+ _idleTimeout = idleTimeout;
+
+ // Do we have an old timeout
+ if (old > 0)
+ {
+ // if the old was less than or equal to the new timeout, then nothing more to do
+ if (old <= idleTimeout)
+ return;
+
+ // old timeout is too long, so cancel it.
+ deactivate();
+ }
+
+ // If we have a new timeout, then check and reschedule
+ if (isOpen())
+ activate();
+ }
+
+ /**
+ * This method should be called when non-idle activity has taken place.
+ */
+ public void notIdle()
+ {
+ _idleTimestamp = System.currentTimeMillis();
+ }
+
+ private void scheduleIdleTimeout(long delay)
+ {
+ Scheduler.Task newTimeout = null;
+ if (isOpen() && delay > 0 && _scheduler != null)
+ newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+ Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ public void onOpen()
+ {
+ activate();
+ }
+
+ private void activate()
+ {
+ if (_idleTimeout > 0)
+ _idleTask.run();
+ }
+
+ public void onClose()
+ {
+ deactivate();
+ }
+
+ private void deactivate()
+ {
+ Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ protected long checkIdleTimeout()
+ {
+ if (isOpen())
+ {
+ long idleTimestamp = getIdleTimestamp();
+ long idleTimeout = getIdleTimeout();
+ long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+ long idleLeft = idleTimeout - idleElapsed;
+
+ LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+ if (idleTimestamp != 0 && idleTimeout > 0)
+ {
+ if (idleLeft <= 0)
+ {
+ LOG.debug("{} idle timeout expired", this);
+ try
+ {
+ onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+ }
+ finally
+ {
+ notIdle();
+ }
+ }
+ }
+
+ return idleLeft >= 0 ? idleLeft : 0;
+ }
+ return -1;
+ }
+
+ /**
+ * This abstract method is called when the idle timeout has expired.
+ *
+ * @param timeout a TimeoutException
+ */
+ protected abstract void onIdleExpired(TimeoutException timeout);
+
+ /**
+ * This abstract method should be called to check if idle timeouts
+ * should still be checked.
+ *
+ * @return True if the entity monitored should still be checked for idle timeouts
+ */
+ public abstract boolean isOpen();
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
new file mode 100644
index 00000000..a6fc7562
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool
+{
+ private static final Logger LOG = Log.getLogger(LeakTrackingByteBufferPool.class);
+
+ private final LeakDetector leakDetector = new LeakDetector()
+ {
+ @Override
+ protected void leaked(LeakInfo leakInfo)
+ {
+ LeakTrackingByteBufferPool.this.leaked(leakInfo);
+ }
+ };
+
+ private final ByteBufferPool delegate;
+
+ public LeakTrackingByteBufferPool(ByteBufferPool delegate)
+ {
+ this.delegate = delegate;
+ addBean(leakDetector);
+ addBean(delegate);
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ ByteBuffer buffer = delegate.acquire(size, direct);
+ if (!leakDetector.acquired(buffer))
+ LOG.warn("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
+ return buffer;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return;
+ if (!leakDetector.released(buffer))
+ LOG.warn("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
+ delegate.release(buffer);
+ }
+
+ protected void leaked(LeakDetector.LeakInfo leakInfo)
+ {
+ LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
new file mode 100644
index 00000000..b331904c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class MappedByteBufferPool implements ByteBufferPool
+{
+ private final ConcurrentMap> directBuffers = new ConcurrentHashMap<>();
+ private final ConcurrentMap> heapBuffers = new ConcurrentHashMap<>();
+ private final int factor;
+
+ public MappedByteBufferPool()
+ {
+ this(1024);
+ }
+
+ public MappedByteBufferPool(int factor)
+ {
+ this.factor = factor;
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ int bucket = bucketFor(size);
+ ConcurrentMap> buffers = buffersFor(direct);
+
+ ByteBuffer result = null;
+ Queue byteBuffers = buffers.get(bucket);
+ if (byteBuffers != null)
+ result = byteBuffers.poll();
+
+ if (result == null)
+ {
+ int capacity = bucket * factor;
+ result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+ }
+
+ BufferUtil.clear(result);
+ return result;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return; // nothing to do
+
+ // validate that this buffer is from this pool
+ assert((buffer.capacity() % factor) == 0);
+
+ int bucket = bucketFor(buffer.capacity());
+ ConcurrentMap> buffers = buffersFor(buffer.isDirect());
+
+ // Avoid to create a new queue every time, just to be discarded immediately
+ Queue byteBuffers = buffers.get(bucket);
+ if (byteBuffers == null)
+ {
+ byteBuffers = new ConcurrentLinkedQueue<>();
+ Queue existing = buffers.putIfAbsent(bucket, byteBuffers);
+ if (existing != null)
+ byteBuffers = existing;
+ }
+
+ BufferUtil.clear(buffer);
+ byteBuffers.offer(buffer);
+ }
+
+ public void clear()
+ {
+ directBuffers.clear();
+ heapBuffers.clear();
+ }
+
+ private int bucketFor(int size)
+ {
+ int bucket = size / factor;
+ if (size % factor > 0)
+ ++bucket;
+ return bucket;
+ }
+
+ // Package local for testing
+ ConcurrentMap> buffersFor(boolean direct)
+ {
+ return direct ? directBuffers : heapBuffers;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
new file mode 100644
index 00000000..cd05630f
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
@@ -0,0 +1,128 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingClientConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(NegotiatingClientConnection.class);
+
+ private final SSLEngine engine;
+ private final ClientConnectionFactory connectionFactory;
+ private final Map context;
+ private volatile boolean completed;
+
+ protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map context)
+ {
+ super(endp, executor);
+ this.engine = sslEngine;
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ }
+
+ protected SSLEngine getSSLEngine()
+ {
+ return engine;
+ }
+
+ protected void completed()
+ {
+ completed = true;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ try
+ {
+ getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+ if (completed)
+ replaceConnection();
+ else
+ fillInterested();
+ }
+ catch (IOException x)
+ {
+ close();
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public void onFillable()
+ {
+ while (true)
+ {
+ int filled = fill();
+ if (filled == 0 && !completed)
+ fillInterested();
+ if (filled <= 0 || completed)
+ break;
+ }
+ if (completed)
+ replaceConnection();
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ close();
+ return -1;
+ }
+ }
+
+ private void replaceConnection()
+ {
+ EndPoint endPoint = getEndPoint();
+ try
+ {
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(endPoint, context);
+ ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ close();
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ // Gentler close for SSL.
+ getEndPoint().shutdownOutput();
+ super.close();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
new file mode 100644
index 00000000..ff386007
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+public abstract class NegotiatingClientConnectionFactory implements ClientConnectionFactory
+{
+ private final ClientConnectionFactory connectionFactory;
+
+ protected NegotiatingClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ public ClientConnectionFactory getClientConnectionFactory()
+ {
+ return connectionFactory;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
new file mode 100644
index 00000000..6a4239f0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -0,0 +1,100 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+/**
+ * A listener for raw network traffic within Jetty.
+ * {@link NetworkTrafficListener}s can be installed in a
+ * org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector
,
+ * and are notified of the following network traffic events:
+ *
+ * Connection opened, when the server has accepted the connection from a remote client
+ * Incoming bytes, when the server receives bytes sent from a remote client
+ * Outgoing bytes, when the server sends bytes to a remote client
+ * Connection closed, when the server has closed the connection to a remote client
+ *
+ * {@link NetworkTrafficListener}s can be used to log the network traffic viewed by
+ * a Jetty server (for example logging to filesystem) for activities such as debugging
+ * or request/response cycles or for replaying request/response cycles to other servers.
+ */
+public interface NetworkTrafficListener
+{
+ /**
+ * Callback method invoked when a connection from a remote client has been accepted.
+ * The {@code socket} parameter can be used to extract socket address information of
+ * the remote client.
+ *
+ * @param socket the socket associated with the remote client
+ */
+ public void opened(Socket socket);
+
+ /**
+ * Callback method invoked when bytes sent by a remote client arrived on the server.
+ *
+ * @param socket the socket associated with the remote client
+ * @param bytes the read-only buffer containing the incoming bytes
+ */
+ public void incoming(Socket socket, ByteBuffer bytes);
+
+ /**
+ * Callback method invoked when bytes are sent to a remote client from the server.
+ * This method is invoked after the bytes have been actually written to the remote client.
+ *
+ * @param socket the socket associated with the remote client
+ * @param bytes the read-only buffer containing the outgoing bytes
+ */
+ public void outgoing(Socket socket, ByteBuffer bytes);
+
+ /**
+ * Callback method invoked when a connection to a remote client has been closed.
+ * The {@code socket} parameter is already closed when this method is called, so it
+ * cannot be queried for socket address information of the remote client.
+ * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
+ * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
+ * in this method.
+ *
+ * @param socket the (closed) socket associated with the remote client
+ */
+ public void closed(Socket socket);
+
+ /**
+ *
A commodity class that implements {@link NetworkTrafficListener} with empty methods.
+ */
+ public static class Adapter implements NetworkTrafficListener
+ {
+ public void opened(Socket socket)
+ {
+ }
+
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ }
+
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ }
+
+ public void closed(Socket socket)
+ {
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644
index 00000000..a4b6f7d2
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
@@ -0,0 +1,155 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+ private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+ private final List listeners;
+
+ public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List listeners) throws IOException
+ {
+ super(channel, selectSet, key, scheduler, idleTimeout);
+ this.listeners = listeners;
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ int read = super.fill(buffer);
+ notifyIncoming(buffer, read);
+ return read;
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ boolean flushed=true;
+ for (ByteBuffer b : buffers)
+ {
+ if (b.hasRemaining())
+ {
+ int position = b.position();
+ ByteBuffer view=b.slice();
+ flushed&=super.flush(b);
+ int l=b.position()-position;
+ view.limit(view.position()+l);
+ notifyOutgoing(view);
+ if (!flushed)
+ break;
+ }
+ }
+ return flushed;
+ }
+
+
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ if (listeners != null && !listeners.isEmpty())
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.opened(getSocket());
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ if (listeners != null && !listeners.isEmpty())
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.closed(getSocket());
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+
+ public void notifyIncoming(ByteBuffer buffer, int read)
+ {
+ if (listeners != null && !listeners.isEmpty() && read > 0)
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ ByteBuffer view = buffer.asReadOnlyBuffer();
+ listener.incoming(getSocket(), view);
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+ public void notifyOutgoing(ByteBuffer view)
+ {
+ if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
+ {
+ Socket socket=getSocket();
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.outgoing(socket, view);
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
new file mode 100644
index 00000000..88c33f73
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer}
+ */
+public class RuntimeIOException extends RuntimeException
+{
+ public RuntimeIOException()
+ {
+ super();
+ }
+
+ public RuntimeIOException(String message)
+ {
+ super(message);
+ }
+
+ public RuntimeIOException(Throwable cause)
+ {
+ super(cause);
+ }
+
+ public RuntimeIOException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
new file mode 100644
index 00000000..30504029
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
@@ -0,0 +1,207 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An ChannelEndpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorManager.SelectableEndPoint
+{
+ public static final Logger LOG = Log.getLogger(SelectChannelEndPoint.class);
+
+ private final Runnable _updateTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ if (getChannel().isOpen())
+ {
+ int oldInterestOps = _key.interestOps();
+ int newInterestOps = _interestOps.get();
+ if (newInterestOps != oldInterestOps)
+ setKeyInterests(oldInterestOps, newInterestOps);
+ }
+ }
+ catch (CancelledKeyException x)
+ {
+ LOG.debug("Ignoring key update for concurrently closed channel {}", this);
+ close();
+ }
+ catch (Exception x)
+ {
+ LOG.warn("Ignoring key update for " + this, x);
+ close();
+ }
+ }
+ };
+
+ /**
+ * true if {@link ManagedSelector#destroyEndPoint(EndPoint)} has not been called
+ */
+ private final AtomicBoolean _open = new AtomicBoolean();
+ private final SelectorManager.ManagedSelector _selector;
+ private final SelectionKey _key;
+ /**
+ * The desired value for {@link SelectionKey#interestOps()}
+ */
+ private final AtomicInteger _interestOps = new AtomicInteger();
+
+ public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+ {
+ super(scheduler,channel);
+ _selector = selector;
+ _key = key;
+ setIdleTimeout(idleTimeout);
+ }
+
+ @Override
+ protected boolean needsFill()
+ {
+ updateLocalInterests(SelectionKey.OP_READ, true);
+ return false;
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ updateLocalInterests(SelectionKey.OP_WRITE, true);
+ }
+
+ @Override
+ public void onSelected()
+ {
+ assert _selector.isSelectorThread();
+ int oldInterestOps = _key.interestOps();
+ int readyOps = _key.readyOps();
+ int newInterestOps = oldInterestOps & ~readyOps;
+ setKeyInterests(oldInterestOps, newInterestOps);
+ updateLocalInterests(readyOps, false);
+ if (_key.isReadable())
+ getFillInterest().fillable();
+ if (_key.isWritable())
+ getWriteFlusher().completeWrite();
+ }
+
+
+ private void updateLocalInterests(int operation, boolean add)
+ {
+ while (true)
+ {
+ int oldInterestOps = _interestOps.get();
+ int newInterestOps;
+ if (add)
+ newInterestOps = oldInterestOps | operation;
+ else
+ newInterestOps = oldInterestOps & ~operation;
+
+ if (isInputShutdown())
+ newInterestOps &= ~SelectionKey.OP_READ;
+ if (isOutputShutdown())
+ newInterestOps &= ~SelectionKey.OP_WRITE;
+
+ if (newInterestOps != oldInterestOps)
+ {
+ if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
+ {
+ LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
+ _selector.updateKey(_updateTask);
+ }
+ else
+ {
+ LOG.debug("Local interests update conflict: now {}, was {}, attempted {} for {}", _interestOps.get(), oldInterestOps, newInterestOps, this);
+ continue;
+ }
+ }
+ else
+ {
+ LOG.debug("Ignoring local interests update {} -> {} for {}", oldInterestOps, newInterestOps, this);
+ }
+ break;
+ }
+ }
+
+
+ private void setKeyInterests(int oldInterestOps, int newInterestOps)
+ {
+ LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
+ _key.interestOps(newInterestOps);
+ }
+
+ @Override
+ public void close()
+ {
+ if (_open.compareAndSet(true, false))
+ {
+ super.close();
+ _selector.destroyEndPoint(this);
+ }
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ // We cannot rely on super.isOpen(), because there is a race between calls to close() and isOpen():
+ // a thread may call close(), which flips the boolean but has not yet called super.close(), and
+ // another thread calls isOpen() which would return true - wrong - if based on super.isOpen().
+ return _open.get();
+ }
+
+ @Override
+ public void onOpen()
+ {
+ if (_open.compareAndSet(false, true))
+ super.onOpen();
+ }
+
+ @Override
+ public String toString()
+ {
+ // Do NOT use synchronized (this)
+ // because it's very easy to deadlock when debugging is enabled.
+ // We do a best effort to print the right toString() and that's it.
+ try
+ {
+ boolean valid = _key!=null && _key.isValid();
+ int keyInterests = valid ? _key.interestOps() : -1;
+ int keyReadiness = valid ? _key.readyOps() : -1;
+ return String.format("%s{io=%d,kio=%d,kro=%d}",
+ super.toString(),
+ _interestOps.get(),
+ keyInterests,
+ keyReadiness);
+ }
+ catch (CancelledKeyException x)
+ {
+ return String.format("%s{io=%s,kio=-2,kro=-2}", super.toString(), _interestOps.get());
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectorManager.java b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
new file mode 100644
index 00000000..fd3c6c6b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
@@ -0,0 +1,984 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * {@link SelectorManager} manages a number of {@link ManagedSelector}s that
+ * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.
+ * {@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+ public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
+ public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
+ protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+ private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false"));
+
+ private final Executor executor;
+ private final Scheduler scheduler;
+ private final ManagedSelector[] _selectors;
+ private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+ private long _selectorIndex;
+
+ protected SelectorManager(Executor executor, Scheduler scheduler)
+ {
+ this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
+ }
+
+ protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ if (selectors<=0)
+ throw new IllegalArgumentException("No selectors");
+ this.executor = executor;
+ this.scheduler = scheduler;
+ _selectors = new ManagedSelector[selectors];
+ }
+
+ public Executor getExecutor()
+ {
+ return executor;
+ }
+
+ public Scheduler getScheduler()
+ {
+ return scheduler;
+ }
+
+ /**
+ * Get the connect timeout
+ *
+ * @return the connect timeout (in milliseconds)
+ */
+ public long getConnectTimeout()
+ {
+ return _connectTimeout;
+ }
+
+ /**
+ * Set the connect timeout (in milliseconds)
+ *
+ * @param milliseconds the number of milliseconds for the timeout
+ */
+ public void setConnectTimeout(long milliseconds)
+ {
+ _connectTimeout = milliseconds;
+ }
+
+ /**
+ * Executes the given task in a different thread.
+ *
+ * @param task the task to execute
+ */
+ protected void execute(Runnable task)
+ {
+ executor.execute(task);
+ }
+
+ /**
+ * @return the number of selectors in use
+ */
+ public int getSelectorCount()
+ {
+ return _selectors.length;
+ }
+
+ private ManagedSelector chooseSelector()
+ {
+ // The ++ increment here is not atomic, but it does not matter,
+ // so long as the value changes sometimes, then connections will
+ // be distributed over the available selectors.
+ long s = _selectorIndex++;
+ int index = (int)(s % getSelectorCount());
+ return _selectors[index];
+ }
+
+ /**
+ * Registers a channel to perform a non-blocking connect.
+ * The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
+ * must be called prior to calling this method.
+ *
+ * @param channel the channel to register
+ * @param attachment the attachment object
+ */
+ public void connect(SocketChannel channel, Object attachment)
+ {
+ ManagedSelector set = chooseSelector();
+ set.submit(set.new Connect(channel, attachment));
+ }
+
+ /**
+ * Registers a channel to perform non-blocking read/write operations.
+ * This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+ * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.
+ *
+ * @param channel the channel to register
+ */
+ public void accept(final SocketChannel channel)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Accept(channel));
+ }
+
+ /**
+ * Registers a server channel for accept operations.
+ * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+ * then the {@link #accepted(SocketChannel)} method is called, which must be
+ * overridden by a derivation of this class to handle the accepted channel
+ *
+ * @param server the server channel to register
+ */
+ public void acceptor(final ServerSocketChannel server)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Acceptor(server));
+ }
+
+ /**
+ * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+ * passed to {@link #acceptor(ServerSocketChannel)}.
+ * The default impl throws an {@link UnsupportedOperationException}, so it must
+ * be overridden by subclasses if a server channel is provided.
+ *
+ * @param channel the
+ * @throws IOException
+ */
+ protected void accepted(SocketChannel channel) throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ for (int i = 0; i < _selectors.length; i++)
+ {
+ ManagedSelector selector = newSelector(i);
+ _selectors[i] = selector;
+ selector.start();
+ execute(new NonBlockingThread(selector));
+ }
+ }
+
+ /**
+ *
Factory method for {@link ManagedSelector}.
+ *
+ * @param id an identifier for the {@link ManagedSelector to create}
+ * @return a new {@link ManagedSelector}
+ */
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(id);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ for (ManagedSelector selector : _selectors)
+ selector.stop();
+ super.doStop();
+ }
+
+ /**
+ * Callback method invoked when an endpoint is opened.
+ *
+ * @param endpoint the endpoint being opened
+ */
+ protected void endPointOpened(EndPoint endpoint)
+ {
+ endpoint.onOpen();
+ }
+
+ /**
+ * Callback method invoked when an endpoint is closed.
+ *
+ * @param endpoint the endpoint being closed
+ */
+ protected void endPointClosed(EndPoint endpoint)
+ {
+ endpoint.onClose();
+ }
+
+ /**
+ * Callback method invoked when a connection is opened.
+ *
+ * @param connection the connection just opened
+ */
+ public void connectionOpened(Connection connection)
+ {
+ try
+ {
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ if (isRunning())
+ LOG.warn("Exception while notifying connection " + connection, x);
+ else
+ LOG.debug("Exception while notifying connection {}",connection, x);
+ }
+ }
+
+ /**
+ * Callback method invoked when a connection is closed.
+ *
+ * @param connection the connection just closed
+ */
+ public void connectionClosed(Connection connection)
+ {
+ try
+ {
+ connection.onClose();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug("Exception while notifying connection " + connection, x);
+ }
+ }
+
+ protected boolean finishConnect(SocketChannel channel) throws IOException
+ {
+ return channel.finishConnect();
+ }
+
+ /**
+ * Callback method invoked when a non-blocking connect cannot be completed.
+ * By default it just logs with level warning.
+ *
+ * @param channel the channel that attempted the connect
+ * @param ex the exception that caused the connect to fail
+ * @param attachment the attachment object associated at registration
+ */
+ protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+ {
+ LOG.warn(String.format("%s - %s", channel, attachment), ex);
+ }
+
+ /**
+ * Factory method to create {@link EndPoint}.
+ * This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+ * or {@link #accept(SocketChannel)}.
+ *
+ * @param channel the channel associated to the endpoint
+ * @param selector the selector the channel is registered to
+ * @param selectionKey the selection key
+ * @return a new endpoint
+ * @throws IOException if the endPoint cannot be created
+ * @see #newConnection(SocketChannel, EndPoint, Object)
+ */
+ protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
+
+ /**
+ * Factory method to create {@link Connection}.
+ *
+ * @param channel the channel associated to the connection
+ * @param endpoint the endpoint
+ * @param attachment the attachment
+ * @return a new connection
+ * @throws IOException
+ * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
+ */
+ public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
+ }
+
+ private enum State
+ {
+ CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+ }
+
+ /**
+ * {@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.
+ * {@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
+ * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
+ * with the channel.
+ */
+ public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+ {
+ private final AtomicReference _state= new AtomicReference<>(State.PROCESS);
+ private final Queue _changes = new ConcurrentArrayQueue<>();
+ private final int _id;
+ private Selector _selector;
+ private volatile Thread _thread;
+
+ public ManagedSelector(int id)
+ {
+ _id = id;
+ setStopTimeout(5000);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _selector = Selector.open();
+ _state.set(State.PROCESS);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ LOG.debug("Stopping {}", this);
+ Stop stop = new Stop();
+ submit(stop);
+ stop.await(getStopTimeout());
+ LOG.debug("Stopped {}", this);
+ }
+
+ /**
+ * Submit a task to update a selector key. If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+ * is set true (default is false), the task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector
+ * woken up if need be.
+ * @param update the update to a key
+ */
+ public void updateKey(Runnable update)
+ {
+ if (__submitKeyUpdates)
+ {
+ submit(update);
+ }
+ else
+ {
+ runChange(update);
+ if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+ wakeup();
+ }
+ }
+
+ /**
+ * Submits a change to be executed in the selector thread.
+ * Changes may be submitted from any thread, and the selector thread woken up
+ * (if necessary) to execute the change.
+ *
+ * @param change the change to submit
+ */
+ public void submit(Runnable change)
+ {
+ // This method may be called from the selector thread, and therefore
+ // we could directly run the change without queueing, but this may
+ // lead to stack overflows on a busy server, so we always offer the
+ // change to the queue and process the state.
+
+ _changes.offer(change);
+ LOG.debug("Queued change {}", change);
+
+ out: while (true)
+ {
+ switch (_state.get())
+ {
+ case SELECT:
+ // Avoid multiple wakeup() calls if we the CAS fails
+ if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+ continue;
+ wakeup();
+ break out;
+ case CHANGES:
+ // Tell the selector thread that we have more changes.
+ // If we fail to CAS, we possibly need to wakeup(), so loop.
+ if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+ break out;
+ continue;
+ case WAKEUP:
+ // Do nothing, we have already a wakeup scheduled
+ break out;
+ case MORE_CHANGES:
+ // Do nothing, we already notified the selector thread of more changes
+ break out;
+ case PROCESS:
+ // Do nothing, the changes will be run after the processing
+ break out;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ private void runChanges()
+ {
+ Runnable change;
+ while ((change = _changes.poll()) != null)
+ runChange(change);
+ }
+
+ protected void runChange(Runnable change)
+ {
+ try
+ {
+ LOG.debug("Running change {}", change);
+ change.run();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug("Could not run change " + change, x);
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ _thread = Thread.currentThread();
+ String name = _thread.getName();
+ try
+ {
+ _thread.setName(name + "-selector-" + SelectorManager.this.getClass().getSimpleName()+"@"+Integer.toHexString(SelectorManager.this.hashCode())+"/"+_id);
+ LOG.debug("Starting {} on {}", _thread, this);
+ while (isRunning())
+ select();
+ runChanges();
+ }
+ finally
+ {
+ LOG.debug("Stopped {} on {}", _thread, this);
+ _thread.setName(name);
+ }
+ }
+
+ /**
+ * Process changes and waits on {@link Selector#select()}.
+ *
+ * @see #submit(Runnable)
+ */
+ public void select()
+ {
+ boolean debug = LOG.isDebugEnabled();
+ try
+ {
+ _state.set(State.CHANGES);
+
+ // Run the changes, and only exit if we ran all changes
+ out: while(true)
+ {
+ switch (_state.get())
+ {
+ case CHANGES:
+ runChanges();
+ if (_state.compareAndSet(State.CHANGES, State.SELECT))
+ break out;
+ continue;
+ case MORE_CHANGES:
+ runChanges();
+ _state.set(State.CHANGES);
+ continue;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ // Must check first for SELECT and *then* for WAKEUP
+ // because we read the state twice in the assert, and
+ // it could change from SELECT to WAKEUP in between.
+ assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
+
+ if (debug)
+ LOG.debug("Selector loop waiting on select");
+ int selected = _selector.select();
+ if (debug)
+ LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
+
+ _state.set(State.PROCESS);
+
+ Set selectedKeys = _selector.selectedKeys();
+ for (SelectionKey key : selectedKeys)
+ {
+ if (key.isValid())
+ {
+ processKey(key);
+ }
+ else
+ {
+ if (debug)
+ LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
+ Object attachment = key.attachment();
+ if (attachment instanceof EndPoint)
+ ((EndPoint)attachment).close();
+ }
+ }
+ selectedKeys.clear();
+ }
+ catch (Throwable x)
+ {
+ if (isRunning())
+ LOG.warn(x);
+ else
+ LOG.ignore(x);
+ }
+ }
+
+ private void processKey(SelectionKey key)
+ {
+ Object attachment = key.attachment();
+ try
+ {
+ if (attachment instanceof SelectableEndPoint)
+ {
+ ((SelectableEndPoint)attachment).onSelected();
+ }
+ else if (key.isConnectable())
+ {
+ processConnect(key, (Connect)attachment);
+ }
+ else if (key.isAcceptable())
+ {
+ processAccept(key);
+ }
+ else
+ {
+ throw new IllegalStateException();
+ }
+ }
+ catch (CancelledKeyException x)
+ {
+ LOG.debug("Ignoring cancelled key for channel {}", key.channel());
+ if (attachment instanceof EndPoint)
+ closeNoExceptions((EndPoint)attachment);
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("Could not process key for channel " + key.channel(), x);
+ if (attachment instanceof EndPoint)
+ closeNoExceptions((EndPoint)attachment);
+ }
+ }
+
+ private void processConnect(SelectionKey key, Connect connect)
+ {
+ SocketChannel channel = (SocketChannel)key.channel();
+ try
+ {
+ key.attach(connect.attachment);
+ boolean connected = finishConnect(channel);
+ if (connected)
+ {
+ connect.timeout.cancel();
+ key.interestOps(0);
+ EndPoint endpoint = createEndPoint(channel, key);
+ key.attach(endpoint);
+ }
+ else
+ {
+ throw new ConnectException();
+ }
+ }
+ catch (Throwable x)
+ {
+ connect.failed(x);
+ }
+ }
+
+ private void processAccept(SelectionKey key)
+ {
+ ServerSocketChannel server = (ServerSocketChannel)key.channel();
+ SocketChannel channel = null;
+ try
+ {
+ while ((channel = server.accept()) != null)
+ {
+ accepted(channel);
+ }
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(channel);
+ LOG.warn("Accept failed for channel " + channel, x);
+ }
+ }
+
+ private void closeNoExceptions(Closeable closeable)
+ {
+ try
+ {
+ if (closeable != null)
+ closeable.close();
+ }
+ catch (Throwable x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
+ public void wakeup()
+ {
+ _selector.wakeup();
+ }
+
+ public boolean isSelectorThread()
+ {
+ return Thread.currentThread() == _thread;
+ }
+
+ private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
+ {
+ EndPoint endPoint = newEndPoint(channel, this, selectionKey);
+ endPointOpened(endPoint);
+ Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
+ endPoint.setConnection(connection);
+ connectionOpened(connection);
+ LOG.debug("Created {}", endPoint);
+ return endPoint;
+ }
+
+ public void destroyEndPoint(EndPoint endPoint)
+ {
+ LOG.debug("Destroyed {}", endPoint);
+ Connection connection = endPoint.getConnection();
+ if (connection != null)
+ connectionClosed(connection);
+ endPointClosed(endPoint);
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
+
+ Thread selecting = _thread;
+
+ Object where = "not selecting";
+ StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
+ if (trace != null)
+ {
+ for (StackTraceElement t : trace)
+ if (t.getClassName().startsWith("org.eclipse.jetty."))
+ {
+ where = t;
+ break;
+ }
+ }
+
+ Selector selector = _selector;
+ if (selector != null && selector.isOpen())
+ {
+ final ArrayList dump = new ArrayList<>(selector.keys().size() * 2);
+ dump.add(where);
+
+ DumpKeys dumpKeys = new DumpKeys(dump);
+ submit(dumpKeys);
+ dumpKeys.await(5, TimeUnit.SECONDS);
+
+ ContainerLifeCycle.dump(out, indent, dump);
+ }
+ }
+
+ public void dumpKeysState(List dumps)
+ {
+ Selector selector = _selector;
+ Set keys = selector.keys();
+ dumps.add(selector + " keys=" + keys.size());
+ for (SelectionKey key : keys)
+ {
+ if (key.isValid())
+ dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
+ else
+ dumps.add(key.attachment() + " iOps=-1 rOps=-1");
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ Selector selector = _selector;
+ return String.format("%s keys=%d selected=%d",
+ super.toString(),
+ selector != null && selector.isOpen() ? selector.keys().size() : -1,
+ selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+ }
+
+ private class DumpKeys implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final List _dumps;
+
+ private DumpKeys(List dumps)
+ {
+ this._dumps = dumps;
+ }
+
+ @Override
+ public void run()
+ {
+ dumpKeysState(_dumps);
+ latch.countDown();
+ }
+
+ public boolean await(long timeout, TimeUnit unit)
+ {
+ try
+ {
+ return latch.await(timeout, unit);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+
+ private class Acceptor implements Runnable
+ {
+ private final ServerSocketChannel _channel;
+
+ public Acceptor(ServerSocketChannel channel)
+ {
+ this._channel = channel;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+ LOG.debug("{} acceptor={}", this, key);
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(_channel);
+ LOG.warn(x);
+ }
+ }
+ }
+
+ private class Accept implements Runnable
+ {
+ private final SocketChannel _channel;
+
+ public Accept(SocketChannel channel)
+ {
+ this._channel = channel;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ SelectionKey key = _channel.register(_selector, 0, null);
+ EndPoint endpoint = createEndPoint(_channel, key);
+ key.attach(endpoint);
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(_channel);
+ LOG.debug(x);
+ }
+ }
+ }
+
+ private class Connect implements Runnable
+ {
+ private final AtomicBoolean failed = new AtomicBoolean();
+ private final SocketChannel channel;
+ private final Object attachment;
+ private final Scheduler.Task timeout;
+
+ public Connect(SocketChannel channel, Object attachment)
+ {
+ this.channel = channel;
+ this.attachment = attachment;
+ this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ channel.register(_selector, SelectionKey.OP_CONNECT, this);
+ }
+ catch (Throwable x)
+ {
+ failed(x);
+ }
+ }
+
+ protected void failed(Throwable failure)
+ {
+ if (failed.compareAndSet(false, true))
+ {
+ timeout.cancel();
+ closeNoExceptions(channel);
+ connectionFailed(channel, failure, attachment);
+ }
+ }
+ }
+
+ private class ConnectTimeout implements Runnable
+ {
+ private final Connect connect;
+
+ private ConnectTimeout(Connect connect)
+ {
+ this.connect = connect;
+ }
+
+ @Override
+ public void run()
+ {
+ SocketChannel channel = connect.channel;
+ if (channel.isConnectionPending())
+ {
+ LOG.debug("Channel {} timed out while connecting, closing it", channel);
+ connect.failed(new SocketTimeoutException());
+ }
+ }
+ }
+
+ private class Stop implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ for (SelectionKey key : _selector.keys())
+ {
+ Object attachment = key.attachment();
+ if (attachment instanceof EndPoint)
+ {
+ EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
+ execute(closer);
+ // We are closing the SelectorManager, so we want to block the
+ // selector thread here until we have closed all EndPoints.
+ // This is different than calling close() directly, because close()
+ // can wait forever, while here we are limited by the stop timeout.
+ closer.await(getStopTimeout());
+ }
+ }
+
+ closeNoExceptions(_selector);
+ }
+ finally
+ {
+ latch.countDown();
+ }
+ }
+
+ public boolean await(long timeout)
+ {
+ try
+ {
+ return latch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+
+ private class EndPointCloser implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final EndPoint endPoint;
+
+ private EndPointCloser(EndPoint endPoint)
+ {
+ this.endPoint = endPoint;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ closeNoExceptions(endPoint.getConnection());
+ }
+ finally
+ {
+ latch.countDown();
+ }
+ }
+
+ private boolean await(long timeout)
+ {
+ try
+ {
+ return latch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
+ * non-blocking events by the {@link ManagedSelector}.
+ */
+ public interface SelectableEndPoint extends EndPoint
+ {
+ /**
+ * Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
+ * for this endpoint.
+ */
+ void onSelected();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java b/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java
new file mode 100644
index 00000000..6898ddfb
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java
@@ -0,0 +1,682 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of
+ * {@link java.io.IOException} thrown by the underlying implementation of
+ * {@link java.io.Writer} as {@link RuntimeIOException} instances.
+ */
+public class UncheckedPrintWriter extends PrintWriter
+{
+ private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class);
+
+ private boolean _autoFlush = false;
+ private IOException _ioException;
+ private boolean _isClosed = false;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Line separator string. This is the value of the line.separator property
+ * at the moment that the stream was created.
+ */
+ private String _lineSeparator;
+
+ public UncheckedPrintWriter(Writer out)
+ {
+ this(out,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter.
+ *
+ * @param out
+ * A character-output stream
+ * @param autoFlush
+ * A boolean; if true, the println() methods will flush the
+ * output buffer
+ */
+ public UncheckedPrintWriter(Writer out, boolean autoFlush)
+ {
+ super(out,autoFlush);
+ this._autoFlush = autoFlush;
+ this._lineSeparator = System.getProperty("line.separator");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter, without automatic line flushing, from an
+ * existing OutputStream. This convenience constructor creates the necessary
+ * intermediate OutputStreamWriter, which will convert characters into bytes
+ * using the default character encoding.
+ *
+ * @param out
+ * An output stream
+ *
+ * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+ */
+ public UncheckedPrintWriter(OutputStream out)
+ {
+ this(out,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter from an existing OutputStream. This convenience
+ * constructor creates the necessary intermediate OutputStreamWriter, which
+ * will convert characters into bytes using the default character encoding.
+ *
+ * @param out
+ * An output stream
+ * @param autoFlush
+ * A boolean; if true, the println() methods will flush the
+ * output buffer
+ *
+ * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+ */
+ public UncheckedPrintWriter(OutputStream out, boolean autoFlush)
+ {
+ this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean checkError()
+ {
+ return _ioException!=null || super.checkError();
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setError(Throwable th)
+ {
+
+ super.setError();
+
+ if (th instanceof IOException)
+ _ioException=(IOException)th;
+ else
+ {
+ _ioException=new IOException(String.valueOf(th));
+ _ioException.initCause(th);
+ }
+
+ LOG.debug(th);
+ }
+
+
+ @Override
+ protected void setError()
+ {
+ setError(new IOException());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check to make sure that the stream has not been closed */
+ private void isOpen() throws IOException
+ {
+ if (_ioException!=null)
+ throw new RuntimeIOException(_ioException);
+
+ if (_isClosed)
+ throw new IOException("Stream closed");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Flush the stream.
+ */
+ @Override
+ public void flush()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.flush();
+ }
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Close the stream.
+ */
+ @Override
+ public void close()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ out.close();
+ _isClosed = true;
+ }
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a single character.
+ *
+ * @param c
+ * int specifying a character to be written.
+ */
+ @Override
+ public void write(int c)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(c);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a portion of an array of characters.
+ *
+ * @param buf
+ * Array of characters
+ * @param off
+ * Offset from which to start writing characters
+ * @param len
+ * Number of characters to write
+ */
+ @Override
+ public void write(char buf[], int off, int len)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(buf,off,len);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write an array of characters. This method cannot be inherited from the
+ * Writer class because it must suppress I/O exceptions.
+ *
+ * @param buf
+ * Array of characters to be written
+ */
+ @Override
+ public void write(char buf[])
+ {
+ this.write(buf,0,buf.length);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a portion of a string.
+ *
+ * @param s
+ * A String
+ * @param off
+ * Offset from which to start writing characters
+ * @param len
+ * Number of characters to write
+ */
+ @Override
+ public void write(String s, int off, int len)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(s,off,len);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a string. This method cannot be inherited from the Writer class
+ * because it must suppress I/O exceptions.
+ *
+ * @param s
+ * String to be written
+ */
+ @Override
+ public void write(String s)
+ {
+ this.write(s,0,s.length());
+ }
+
+ private void newLine()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(_lineSeparator);
+ if (_autoFlush)
+ out.flush();
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a boolean value. The string produced by {@link
+ * java.lang.String#valueOf(boolean)}
is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link
+ * #write(int)}
method.
+ *
+ * @param b
+ * The boolean
to be printed
+ */
+ @Override
+ public void print(boolean b)
+ {
+ this.write(b?"true":"false");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a character. The character is translated into one or more bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link
+ * #write(int)}
method.
+ *
+ * @param c
+ * The char
to be printed
+ */
+ @Override
+ public void print(char c)
+ {
+ this.write(c);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an integer. The string produced by {@link
+ * java.lang.String#valueOf(int)}
is translated into bytes according
+ * to the platform's default character encoding, and these bytes are written
+ * in exactly the manner of the {@link #write(int)}
method.
+ *
+ * @param i
+ * The int
to be printed
+ * @see java.lang.Integer#toString(int)
+ */
+ @Override
+ public void print(int i)
+ {
+ this.write(String.valueOf(i));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a long integer. The string produced by {@link
+ * java.lang.String#valueOf(long)}
is translated into bytes according
+ * to the platform's default character encoding, and these bytes are written
+ * in exactly the manner of the {@link #write(int)}
method.
+ *
+ * @param l
+ * The long
to be printed
+ * @see java.lang.Long#toString(long)
+ */
+ @Override
+ public void print(long l)
+ {
+ this.write(String.valueOf(l));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a floating-point number. The string produced by {@link
+ * java.lang.String#valueOf(float)}
is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param f
+ * The float
to be printed
+ * @see java.lang.Float#toString(float)
+ */
+ @Override
+ public void print(float f)
+ {
+ this.write(String.valueOf(f));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a double-precision floating-point number. The string produced by
+ * {@link java.lang.String#valueOf(double)}
is translated into
+ * bytes according to the platform's default character encoding, and these
+ * bytes are written in exactly the manner of the {@link
+ * #write(int)}
method.
+ *
+ * @param d
+ * The double
to be printed
+ * @see java.lang.Double#toString(double)
+ */
+ @Override
+ public void print(double d)
+ {
+ this.write(String.valueOf(d));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an array of characters. The characters are converted into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param s
+ * The array of chars to be printed
+ *
+ * @throws NullPointerException
+ * If s
is null
+ */
+ @Override
+ public void print(char s[])
+ {
+ this.write(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a string. If the argument is null
then the string
+ * "null"
is printed. Otherwise, the string's characters are
+ * converted into bytes according to the platform's default character
+ * encoding, and these bytes are written in exactly the manner of the
+ * {@link #write(int)}
method.
+ *
+ * @param s
+ * The String
to be printed
+ */
+ @Override
+ public void print(String s)
+ {
+ if (s == null)
+ {
+ s = "null";
+ }
+ this.write(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an object. The string produced by the {@link
+ * java.lang.String#valueOf(Object)}
method is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the {@link #write(int)}
+ * method.
+ *
+ * @param obj
+ * The Object
to be printed
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public void print(Object obj)
+ {
+ this.write(String.valueOf(obj));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Terminate the current line by writing the line separator string. The line
+ * separator string is defined by the system property
+ * line.separator
, and is not necessarily a single newline
+ * character ('\n'
).
+ */
+ @Override
+ public void println()
+ {
+ this.newLine();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a boolean value and then terminate the line. This method behaves as
+ * though it invokes {@link #print(boolean)}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the boolean
value to be printed
+ */
+ @Override
+ public void println(boolean x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a character and then terminate the line. This method behaves as
+ * though it invokes {@link #print(char)}
and then {@link
+ * #println()}
.
+ *
+ * @param x
+ * the char
value to be printed
+ */
+ @Override
+ public void println(char x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an integer and then terminate the line. This method behaves as
+ * though it invokes {@link #print(int)}
and then {@link
+ * #println()}
.
+ *
+ * @param x
+ * the int
value to be printed
+ */
+ @Override
+ public void println(int x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a long integer and then terminate the line. This method behaves as
+ * though it invokes {@link #print(long)}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the long
value to be printed
+ */
+ @Override
+ public void println(long x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a floating-point number and then terminate the line. This method
+ * behaves as though it invokes {@link #print(float)}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the float
value to be printed
+ */
+ @Override
+ public void println(float x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a double-precision floating-point number and then terminate the
+ * line. This method behaves as though it invokes {@link
+ * #print(double)}
and then {@link #println()}
.
+ *
+ * @param x
+ * the double
value to be printed
+ */
+ /* ------------------------------------------------------------ */
+ @Override
+ public void println(double x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an array of characters and then terminate the line. This method
+ * behaves as though it invokes {@link #print(char[])}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the array of char
values to be printed
+ */
+ @Override
+ public void println(char x[])
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a String and then terminate the line. This method behaves as though
+ * it invokes {@link #print(String)}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the String
value to be printed
+ */
+ @Override
+ public void println(String x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an Object and then terminate the line. This method behaves as
+ * though it invokes {@link #print(Object)}
and then
+ * {@link #println()}
.
+ *
+ * @param x
+ * the Object
value to be printed
+ */
+ @Override
+ public void println(Object x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java b/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java
new file mode 100644
index 00000000..fccc6229
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java
@@ -0,0 +1,504 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.WritePendingException;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
+ * {@link EndPoint#flush(ByteBuffer...)} until all content is written.
+ * The abstract method {@link #onIncompleteFlushed()} is called when not all content has been written after a call to
+ * flush and should organise for the {@link #completeWrite()} method to be called when a subsequent call to flush
+ * should be able to make more progress.
+ *
+ */
+abstract public class WriteFlusher
+{
+ private static final Logger LOG = Log.getLogger(WriteFlusher.class);
+ private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+ private static final ByteBuffer[] EMPTY_BUFFERS = new ByteBuffer[0];
+ private static final EnumMap> __stateTransitions = new EnumMap<>(StateType.class);
+ private static final State __IDLE = new IdleState();
+ private static final State __WRITING = new WritingState();
+ private static final State __COMPLETING = new CompletingState();
+ private final EndPoint _endPoint;
+ private final AtomicReference _state = new AtomicReference<>();
+
+ static
+ {
+ // fill the state machine
+ __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING));
+ __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+ __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE));
+ __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+ __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE));
+ }
+
+ // A write operation may either complete immediately:
+ // IDLE-->WRITING-->IDLE
+ // Or it may not completely flush and go via the PENDING state
+ // IDLE-->WRITING-->PENDING-->COMPLETING-->IDLE
+ // Or it may take several cycles to complete
+ // IDLE-->WRITING-->PENDING-->COMPLETING-->PENDING-->COMPLETING-->IDLE
+ //
+ // If a failure happens while in IDLE, it is a noop since there is no operation to tell of the failure.
+ // If a failure happens while in WRITING, but the the write has finished successfully or with an IOExceptions,
+ // the callback's complete or respectively failed methods will be called.
+ // If a failure happens in PENDING state, then the fail method calls the pending callback and moves to IDLE state
+ //
+ // IDLE--(fail)-->IDLE
+ // IDLE-->WRITING--(fail)-->FAILED-->IDLE
+ // IDLE-->WRITING-->PENDING--(fail)-->IDLE
+ // IDLE-->WRITING-->PENDING-->COMPLETING--(fail)-->FAILED-->IDLE
+ //
+ // So a call to fail in the PENDING state will be directly handled and the state changed to IDLE
+ // A call to fail in the WRITING or COMPLETING states will just set the state to FAILED and the failure will be
+ // handled with the write or completeWrite methods try to move the state from what they thought it was.
+ //
+
+ protected WriteFlusher(EndPoint endPoint)
+ {
+ _state.set(__IDLE);
+ _endPoint = endPoint;
+ }
+
+ private enum StateType
+ {
+ IDLE,
+ WRITING,
+ PENDING,
+ COMPLETING,
+ FAILED
+ }
+
+ /**
+ * Tries to update the current state to the given new state.
+ * @param previous the expected current state
+ * @param next the desired new state
+ * @return the previous state or null if the state transition failed
+ * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error)
+ */
+ private boolean updateState(State previous,State next)
+ {
+ if (!isTransitionAllowed(previous,next))
+ throw new IllegalStateException();
+
+ boolean updated = _state.compareAndSet(previous, next);
+ if (DEBUG)
+ LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next);
+ return updated;
+ }
+
+ private void fail(PendingState pending)
+ {
+ State current = _state.get();
+ if (current.getType()==StateType.FAILED)
+ {
+ FailedState failed=(FailedState)current;
+ if (updateState(failed,__IDLE))
+ {
+ pending.fail(failed.getCause());
+ return;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ private void ignoreFail()
+ {
+ State current = _state.get();
+ while (current.getType()==StateType.FAILED)
+ {
+ if (updateState(current,__IDLE))
+ return;
+ current = _state.get();
+ }
+ }
+
+ private boolean isTransitionAllowed(State currentState, State newState)
+ {
+ Set allowedNewStateTypes = __stateTransitions.get(currentState.getType());
+ if (!allowedNewStateTypes.contains(newState.getType()))
+ {
+ LOG.warn("{}: {} -> {} not allowed", this, currentState, newState);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * State represents a State of WriteFlusher.
+ */
+ private static class State
+ {
+ private final StateType _type;
+
+ private State(StateType stateType)
+ {
+ _type = stateType;
+ }
+
+ public StateType getType()
+ {
+ return _type;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s", _type);
+ }
+ }
+
+ /**
+ * In IdleState WriteFlusher is idle and accepts new writes
+ */
+ private static class IdleState extends State
+ {
+ private IdleState()
+ {
+ super(StateType.IDLE);
+ }
+ }
+
+ /**
+ * In WritingState WriteFlusher is currently writing.
+ */
+ private static class WritingState extends State
+ {
+ private WritingState()
+ {
+ super(StateType.WRITING);
+ }
+ }
+
+ /**
+ * In FailedState no more operations are allowed. The current implementation will never recover from this state.
+ */
+ private static class FailedState extends State
+ {
+ private final Throwable _cause;
+ private FailedState(Throwable cause)
+ {
+ super(StateType.FAILED);
+ _cause=cause;
+ }
+
+ public Throwable getCause()
+ {
+ return _cause;
+ }
+ }
+
+ /**
+ * In CompletingState WriteFlusher is flushing buffers that have not been fully written in write(). If write()
+ * didn't flush all buffers in one go, it'll switch the State to PendingState. completeWrite() will then switch to
+ * this state and try to flush the remaining buffers.
+ */
+ private static class CompletingState extends State
+ {
+ private CompletingState()
+ {
+ super(StateType.COMPLETING);
+ }
+ }
+
+ /**
+ * In PendingState not all buffers could be written in one go. Then write() will switch to PendingState() and
+ * preserve the state by creating a new PendingState object with the given parameters.
+ */
+ private class PendingState extends State
+ {
+ private final Callback _callback;
+ private final ByteBuffer[] _buffers;
+
+ private PendingState(ByteBuffer[] buffers, Callback callback)
+ {
+ super(StateType.PENDING);
+ _buffers = compact(buffers);
+ _callback = callback;
+ }
+
+ public ByteBuffer[] getBuffers()
+ {
+ return _buffers;
+ }
+
+ protected boolean fail(Throwable cause)
+ {
+ if (_callback!=null)
+ {
+ _callback.failed(cause);
+ return true;
+ }
+ return false;
+ }
+
+ protected void complete()
+ {
+ if (_callback!=null)
+ _callback.succeeded();
+ }
+
+ /**
+ * Compacting the buffers is needed because the semantic of WriteFlusher is
+ * to write the buffers and if the caller sees that the buffer is consumed,
+ * then it can recycle it.
+ * If we do not compact, then it is possible that we store a consumed buffer,
+ * which is then recycled and refilled; when the WriteFlusher is invoked to
+ * complete the write, it will write the refilled bytes, garbling the content.
+ *
+ * @param buffers the buffers to compact
+ * @return the compacted buffers
+ */
+ private ByteBuffer[] compact(ByteBuffer[] buffers)
+ {
+ int length = buffers.length;
+
+ // Just one element, no need to compact
+ if (length < 2)
+ return buffers;
+
+ // How many still have content ?
+ int consumed = 0;
+ while (consumed < length && BufferUtil.isEmpty(buffers[consumed]))
+ ++consumed;
+
+ // All of them still have content, no need to compact
+ if (consumed == 0)
+ return buffers;
+
+ // None has content, return empty
+ if (consumed == length)
+ return EMPTY_BUFFERS;
+
+ return Arrays.copyOfRange(buffers,consumed,length);
+ }
+ }
+
+ /**
+ * Abstract call to be implemented by specific WriteFlushers. It should schedule a call to {@link #completeWrite()}
+ * or {@link #onFail(Throwable)} when appropriate.
+ */
+ abstract protected void onIncompleteFlushed();
+
+ /**
+ * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
+ * fails it'll fail the callback.
+ *
+ * If not all buffers can be written in one go it creates a new PendingState
object to preserve the state
+ * and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
+ *
+ * If all buffers have been written it calls callback.complete().
+ *
+ * @param callback the callback to call on either failed or complete
+ * @param buffers the buffers to flush to the endpoint
+ */
+ public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ {
+ if (DEBUG)
+ LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+
+ if (!updateState(__IDLE,__WRITING))
+ throw new WritePendingException();
+
+ try
+ {
+ boolean flushed=_endPoint.flush(buffers);
+ if (DEBUG)
+ LOG.debug("flushed {}", flushed);
+
+ // Are we complete?
+ for (ByteBuffer b : buffers)
+ {
+ if (!flushed||BufferUtil.hasContent(b))
+ {
+ PendingState pending=new PendingState(buffers, callback);
+ if (updateState(__WRITING,pending))
+ onIncompleteFlushed();
+ else
+ fail(pending);
+ return;
+ }
+ }
+
+ // If updateState didn't succeed, we don't care as our buffers have been written
+ if (!updateState(__WRITING,__IDLE))
+ ignoreFail();
+ if (callback!=null)
+ callback.succeeded();
+ }
+ catch (IOException e)
+ {
+ if (DEBUG)
+ LOG.debug("write exception", e);
+ if (updateState(__WRITING,__IDLE))
+ {
+ if (callback!=null)
+ callback.failed(e);
+ }
+ else
+ fail(new PendingState(buffers, callback));
+ }
+ }
+
+
+ /**
+ * Complete a write that has not completed and that called {@link #onIncompleteFlushed()} to request a call to this
+ * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
+ *
+ * It tries to switch from PENDING to COMPLETING. If state transition fails, then it does nothing as the callback
+ * should have been already failed. That's because the only way to switch from PENDING outside this method is
+ * {@link #onFail(Throwable)} or {@link #onClose()}
+ */
+ public void completeWrite()
+ {
+ if (DEBUG)
+ LOG.debug("completeWrite: {}", this);
+
+ State previous = _state.get();
+
+ if (previous.getType()!=StateType.PENDING)
+ return; // failure already handled.
+
+ PendingState pending = (PendingState)previous;
+ if (!updateState(pending,__COMPLETING))
+ return; // failure already handled.
+
+ try
+ {
+ ByteBuffer[] buffers = pending.getBuffers();
+
+ boolean flushed=_endPoint.flush(buffers);
+ if (DEBUG)
+ LOG.debug("flushed {}", flushed);
+
+ // Are we complete?
+ for (ByteBuffer b : buffers)
+ {
+ if (!flushed || BufferUtil.hasContent(b))
+ {
+ if (updateState(__COMPLETING,pending))
+ onIncompleteFlushed();
+ else
+ fail(pending);
+ return;
+ }
+ }
+
+ // If updateState didn't succeed, we don't care as our buffers have been written
+ if (!updateState(__COMPLETING,__IDLE))
+ ignoreFail();
+ pending.complete();
+ }
+ catch (IOException e)
+ {
+ if (DEBUG)
+ LOG.debug("completeWrite exception", e);
+ if(updateState(__COMPLETING,__IDLE))
+ pending.fail(e);
+ else
+ fail(pending);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Notify the flusher of a failure
+ * @param cause The cause of the failure
+ * @return true if the flusher passed the failure to a {@link Callback} instance
+ */
+ public boolean onFail(Throwable cause)
+ {
+ // Keep trying to handle the failure until we get to IDLE or FAILED state
+ while(true)
+ {
+ State current=_state.get();
+ switch(current.getType())
+ {
+ case IDLE:
+ case FAILED:
+ if (DEBUG)
+ LOG.debug("ignored: {} {}", this, cause);
+ return false;
+
+ case PENDING:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
+ PendingState pending = (PendingState)current;
+ if (updateState(pending,__IDLE))
+ return pending.fail(cause);
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
+ if (updateState(current,new FailedState(cause)))
+ return false;
+ break;
+ }
+ }
+ }
+
+ public void onClose()
+ {
+ if (_state.get()==__IDLE)
+ return;
+ onFail(new ClosedChannelException());
+ }
+
+ boolean isIdle()
+ {
+ return _state.get().getType() == StateType.IDLE;
+ }
+
+ public boolean isInProgress()
+ {
+ switch(_state.get().getType())
+ {
+ case WRITING:
+ case PENDING:
+ case COMPLETING:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java b/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java
new file mode 100644
index 00000000..d08b4736
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/* ------------------------------------------------------------ */
+/** Wrap a Writer as an OutputStream.
+ * When all you have is a Writer and only an OutputStream will do.
+ * Try not to use this as it indicates that your design is a dogs
+ * breakfast (JSP made me write it).
+ *
+ */
+public class WriterOutputStream extends OutputStream
+{
+ protected final Writer _writer;
+ protected final Charset _encoding;
+ private final byte[] _buf=new byte[1];
+
+ /* ------------------------------------------------------------ */
+ public WriterOutputStream(Writer writer, String encoding)
+ {
+ _writer=writer;
+ _encoding=encoding==null?null:Charset.forName(encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ public WriterOutputStream(Writer writer)
+ {
+ _writer=writer;
+ _encoding=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ _writer.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void flush()
+ throws IOException
+ {
+ _writer.flush();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(byte[] b)
+ throws IOException
+ {
+ if (_encoding==null)
+ _writer.write(new String(b));
+ else
+ _writer.write(new String(b,_encoding));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (_encoding==null)
+ _writer.write(new String(b,off,len));
+ else
+ _writer.write(new String(b,off,len,_encoding));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public synchronized void write(int b)
+ throws IOException
+ {
+ _buf[0]=(byte)b;
+ write(_buf);
+ }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/io/package-info.java b/lib/jetty/org/eclipse/jetty/io/package-info.java
new file mode 100644
index 00000000..6316823e
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty IO : Core classes for Jetty IO subsystem
+ */
+package org.eclipse.jetty.io;
+
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644
index 00000000..ce3be14a
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+ public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+ public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+ public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+ private final SslContextFactory sslContextFactory;
+ private final ByteBufferPool byteBufferPool;
+ private final Executor executor;
+ private final ClientConnectionFactory connectionFactory;
+
+ public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ this.byteBufferPool = byteBufferPool;
+ this.executor = executor;
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ {
+ String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+ int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+ SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+ engine.setUseClientMode(true);
+ context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+ SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+ endPoint.setConnection(sslConnection);
+ EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+ appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+ return sslConnection;
+ }
+
+ protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+ {
+ return new SslConnection(byteBufferPool, executor, endPoint, engine);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java
new file mode 100644
index 00000000..37e81111
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -0,0 +1,929 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AbstractEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.FillInterest;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.WriteFlusher;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A Connection that acts as an interceptor between an EndPoint providing SSL encrypted data
+ * and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
+ * wants unencrypted data.
+ *
+ * The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data. It then provides an endpoint via {@link #getDecryptedEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ *
+ * The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
+ * asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
+ *
+ * The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
+ * effort attempts to progress the connection using only calls to the encrypted {@link EndPoint#fill(ByteBuffer)} and {@link EndPoint#flush(ByteBuffer...)}
+ * methods. They will never block nor schedule any readInterest or write callbacks. If a fill/flush cannot progress either because
+ * of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
+ * Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
+ * encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
+ *
+ * It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
+ * methods. For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
+ * write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
+ * to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
+ *
+ * MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
+ * themselves. Instead they simple make the callbacks to the decrypted callbacks, so that the passive encrypted fill/flush will
+ * be called again and make another best effort attempt to progress the connection.
+ *
+ */
+public class SslConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(SslConnection.class);
+ private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+ private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
+ private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
+ private final ByteBufferPool _bufferPool;
+ private SSLEngine _sslEngine;
+ private final SslReconfigurator _sslFactory;
+ private final DecryptedEndPoint _decryptedEndPoint;
+ private ByteBuffer _decryptedInput;
+ private ByteBuffer _encryptedInput;
+ private ByteBuffer _encryptedOutput;
+ private final boolean _encryptedDirectBuffers = false;
+ private final boolean _decryptedDirectBuffers = false;
+ private final Runnable _runCompletWrite = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ _decryptedEndPoint.getWriteFlusher().completeWrite();
+ }
+ };
+ private boolean _renegotiationAllowed;
+
+ public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+ {
+ this(byteBufferPool, executor, endPoint, sslEngine, null);
+ }
+ public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine, SslReconfigurator fact)
+ {
+ // This connection does not execute calls to onfillable, so they will be called by the selector thread.
+ // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
+ super(endPoint, executor, !EXECUTE_ONFILLABLE);
+ this._bufferPool = byteBufferPool;
+ this._sslEngine = sslEngine;
+ this._sslFactory = fact;
+ this._decryptedEndPoint = newDecryptedEndPoint();
+ }
+
+ protected DecryptedEndPoint newDecryptedEndPoint()
+ {
+ return new DecryptedEndPoint();
+ }
+
+ public SSLEngine getSSLEngine()
+ {
+ return _sslEngine;
+ }
+
+ public DecryptedEndPoint getDecryptedEndPoint()
+ {
+ return _decryptedEndPoint;
+ }
+
+ public boolean isRenegotiationAllowed()
+ {
+ return _renegotiationAllowed;
+ }
+
+ public void setRenegotiationAllowed(boolean renegotiationAllowed)
+ {
+ this._renegotiationAllowed = renegotiationAllowed;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ try
+ {
+ // Begin the handshake
+ _sslEngine.beginHandshake();
+ super.onOpen();
+ getDecryptedEndPoint().getConnection().onOpen();
+ }
+ catch (SSLException x)
+ {
+ getEndPoint().close();
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ _decryptedEndPoint.getConnection().onClose();
+ super.onClose();
+ }
+
+ @Override
+ public void close()
+ {
+ getDecryptedEndPoint().getConnection().close();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ // onFillable means that there are encrypted bytes ready to be filled.
+ // however we do not fill them here on this callback, but instead wakeup
+ // the decrypted readInterest and/or writeFlusher so that they will attempt
+ // to do the fill and/or flush again and these calls will do the actually
+ // filling.
+
+ if (DEBUG)
+ LOG.debug("onFillable enter {}", _decryptedEndPoint);
+
+ // We have received a close handshake, close the end point to send FIN.
+ if (_decryptedEndPoint.isInputShutdown())
+ _decryptedEndPoint.close();
+
+ // wake up whoever is doing the fill or the flush so they can
+ // do all the filling, unwrapping, wrapping and flushing
+ _decryptedEndPoint.getFillInterest().fillable();
+
+ // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+ synchronized(_decryptedEndPoint)
+ {
+ if (_decryptedEndPoint._flushRequiresFillToProgress)
+ {
+ _decryptedEndPoint._flushRequiresFillToProgress = false;
+ getExecutor().execute(_runCompletWrite);
+ }
+ }
+
+ if (DEBUG)
+ LOG.debug("onFillable exit {}", _decryptedEndPoint);
+ }
+
+ @Override
+ public void onFillInterestedFailed(Throwable cause)
+ {
+ // this means that the fill interest in encrypted bytes has failed.
+ // However we do not handle that here on this callback, but instead wakeup
+ // the decrypted readInterest and/or writeFlusher so that they will attempt
+ // to do the fill and/or flush again and these calls will do the actually
+ // handle the cause.
+ _decryptedEndPoint.getFillInterest().onFail(cause);
+
+ boolean failFlusher = false;
+ synchronized(_decryptedEndPoint)
+ {
+ if (_decryptedEndPoint._flushRequiresFillToProgress)
+ {
+ _decryptedEndPoint._flushRequiresFillToProgress = false;
+ failFlusher = true;
+ }
+ }
+ if (failFlusher)
+ _decryptedEndPoint.getWriteFlusher().onFail(cause);
+ }
+
+ @Override
+ public String toString()
+ {
+ ByteBuffer b = _encryptedInput;
+ int ei=b==null?-1:b.remaining();
+ b = _encryptedOutput;
+ int eo=b==null?-1:b.remaining();
+ b = _decryptedInput;
+ int di=b==null?-1:b.remaining();
+
+ return String.format("SslConnection@%x{%s,eio=%d/%d,di=%d} -> %s",
+ hashCode(),
+ _sslEngine.getHandshakeStatus(),
+ ei,eo,di,
+ _decryptedEndPoint.getConnection());
+ }
+
+ public class DecryptedEndPoint extends AbstractEndPoint
+ {
+ private boolean _fillRequiresFlushToProgress;
+ private boolean _flushRequiresFillToProgress;
+ private boolean _cannotAcceptMoreAppDataToFlush;
+ private boolean _handshaken;
+ private boolean _underFlown;
+ private boolean _peeking = _sslFactory != null;
+
+ private final Callback _writeCallback = new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ // This means that a write of encrypted data has completed. Writes are done
+ // only if there is a pending writeflusher or a read needed to write
+ // data. In either case the appropriate callback is passed on.
+ boolean fillable = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("write.complete {}", SslConnection.this.getEndPoint());
+
+ releaseEncryptedOutputBuffer();
+
+ _cannotAcceptMoreAppDataToFlush = false;
+
+ if (_fillRequiresFlushToProgress)
+ {
+ _fillRequiresFlushToProgress = false;
+ fillable = true;
+ }
+ }
+ if (fillable)
+ getFillInterest().fillable();
+ getExecutor().execute(_runCompletWrite);
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ // This means that a write of data has failed. Writes are done
+ // only if there is an active writeflusher or a read needed to write
+ // data. In either case the appropriate callback is passed on.
+ boolean fail_filler = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("{} write.failed", SslConnection.this, x);
+ BufferUtil.clear(_encryptedOutput);
+ releaseEncryptedOutputBuffer();
+
+ _cannotAcceptMoreAppDataToFlush = false;
+
+ if (_fillRequiresFlushToProgress)
+ {
+ _fillRequiresFlushToProgress = false;
+ fail_filler = true;
+ }
+ }
+
+ final boolean filler_failed=fail_filler;
+
+ failedCallback(new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (filler_failed)
+ getFillInterest().onFail(x);
+ getWriteFlusher().onFail(x);
+ }
+
+ },x);
+ }
+ };
+
+ public DecryptedEndPoint()
+ {
+ super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
+ setIdleTimeout(getEndPoint().getIdleTimeout());
+ }
+
+ @Override
+ protected FillInterest getFillInterest()
+ {
+ return super.getFillInterest();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeout)
+ {
+ super.setIdleTimeout(idleTimeout);
+ getEndPoint().setIdleTimeout(idleTimeout);
+ }
+
+ @Override
+ protected WriteFlusher getWriteFlusher()
+ {
+ return super.getWriteFlusher();
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ // This means that the decrypted endpoint write method was called and not
+ // all data could be wrapped. So either we need to write some encrypted data,
+ // OR if we are handshaking we need to read some encrypted data OR
+ // if neither then we should just try the flush again.
+ boolean flush = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("onIncompleteFlush {}", getEndPoint());
+ // If we have pending output data,
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ // write it
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().write(_writeCallback, _encryptedOutput);
+ }
+ // If we are handshaking and need to read,
+ else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
+ {
+ // check if we are actually read blocked in order to write
+ _flushRequiresFillToProgress = true;
+ SslConnection.this.fillInterested();
+ }
+ else
+ {
+ flush = true;
+ }
+ }
+ if (flush)
+ {
+ // If the output is closed,
+ if (isOutputShutdown())
+ {
+ // don't bother writing, just notify of close
+ getWriteFlusher().onClose();
+ }
+ // Else,
+ else
+ {
+ // try to flush what is pending
+ getWriteFlusher().completeWrite();
+ }
+ }
+ }
+
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ // This means that the decrypted data consumer has called the fillInterested
+ // method on the DecryptedEndPoint, so we have to work out if there is
+ // decrypted data to be filled or what callbacks to setup to be told when there
+ // might be more encrypted data available to attempt another call to fill
+
+ synchronized (DecryptedEndPoint.this)
+ {
+ // Do we already have some app data, then app can fill now so return true
+ if (BufferUtil.hasContent(_decryptedInput))
+ return true;
+
+ // If we have no encrypted data to decrypt OR we have some, but it is not enough
+ if (BufferUtil.isEmpty(_encryptedInput) || _underFlown)
+ {
+ // We are not ready to read data
+
+ // Are we actually write blocked?
+ if (_fillRequiresFlushToProgress)
+ {
+ // we must be blocked trying to write before we can read
+
+ // Do we have data to write
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ // write it
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().write(_writeCallback, _encryptedOutput);
+ }
+ else
+ {
+ // we have already written the net data
+ // pretend we are readable so the wrap is done by next readable callback
+ _fillRequiresFlushToProgress = false;
+ return true;
+ }
+ }
+ else
+ {
+ // Normal readable callback
+ // Get called back on onfillable when then is more data to fill
+ SslConnection.this.fillInterested();
+ }
+
+ return false;
+ }
+ else
+ {
+ // We are ready to read data
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public void setConnection(Connection connection)
+ {
+ if (connection instanceof AbstractConnection)
+ {
+ AbstractConnection a = (AbstractConnection)connection;
+ if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize())
+ a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize());
+ }
+ super.setConnection(connection);
+ }
+
+ public SslConnection getSslConnection()
+ {
+ return SslConnection.this;
+ }
+
+ @Override
+ public synchronized int fill(ByteBuffer buffer) throws IOException
+ {
+ if (DEBUG)
+ LOG.debug("{} fill enter", SslConnection.this);
+ try
+ {
+ // Do we already have some decrypted data?
+ if (BufferUtil.hasContent(_decryptedInput))
+ return BufferUtil.append(buffer,_decryptedInput);
+
+ // We will need a network buffer
+ if (_encryptedInput == null)
+ _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+ else if(!_peeking)
+ BufferUtil.compact(_encryptedInput);
+
+ // We also need an app buffer, but can use the passed buffer if it is big enough
+ ByteBuffer app_in;
+ if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
+ app_in = buffer;
+ else if (_decryptedInput == null)
+ app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
+ else
+ app_in = _decryptedInput;
+
+ // loop filling and unwrapping until we have something
+ while (true)
+ {
+ // Let's try reading some encrypted data... even if we have some already.
+ int net_filled = getEndPoint().fill(_encryptedInput);
+ if (DEBUG)
+ LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
+
+ decryption: while (true)
+ {
+ // Let's unwrap even if we have no net data because in that
+ // case we want to fall through to the handshake handling
+ int pos = BufferUtil.flipToFill(app_in);
+ SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+ BufferUtil.flipToFlush(app_in, pos);
+ if (DEBUG)
+ LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+ HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+ Status unwrapResultStatus = unwrapResult.getStatus();
+
+ _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
+
+ if (_underFlown)
+ {
+ if (net_filled < 0)
+ closeInbound();
+ if (net_filled <= 0)
+ return net_filled;
+ }
+
+ switch (unwrapResultStatus)
+ {
+ case CLOSED:
+ {
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ {
+ // We were not handshaking, so just tell the app we are closed
+ return -1;
+ }
+ case NEED_TASK:
+ {
+ _sslEngine.getDelegatedTask().run();
+ continue;
+ }
+ case NEED_WRAP:
+ {
+ // We need to send some handshake data (probably the close handshake).
+ // We return -1 so that the application can drive the close by flushing
+ // or shutting down the output.
+ return -1;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ case BUFFER_UNDERFLOW:
+ case OK:
+ {
+ if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} {} handshake completed", SslConnection.this,
+ _sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
+ }
+
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ closeInbound();
+ return -1;
+ }
+
+ // If bytes were produced, don't bother with the handshake status;
+ // pass the decrypted data to the application, which will perform
+ // another call to fill() or flush().
+ if (unwrapResult.bytesProduced() > 0)
+ {
+ if (app_in == buffer)
+ return unwrapResult.bytesProduced();
+ return BufferUtil.append(buffer,_decryptedInput);
+ }
+
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ {
+ if (_underFlown)
+ break decryption;
+ continue;
+ }
+ case NEED_TASK:
+ {
+ _sslEngine.getDelegatedTask().run();
+ if(_peeking)
+ {
+ _sslEngine = _sslFactory.restartSSL(_sslEngine.getHandshakeSession());
+ _encryptedInput.position(0);
+ _peeking = false;
+ continue decryption;
+ }
+ continue;
+ }
+ case NEED_WRAP:
+ {
+ // If we are called from flush()
+ // return to let it do the wrapping.
+ if (buffer == __FLUSH_CALLED_FILL)
+ return 0;
+
+ _fillRequiresFlushToProgress = true;
+ flush(__FILL_CALLED_FLUSH);
+ if (BufferUtil.isEmpty(_encryptedOutput))
+ {
+ // The flush wrote all the encrypted bytes so continue to fill
+ _fillRequiresFlushToProgress = false;
+ continue;
+ }
+ else
+ {
+ // The flush did not complete, return from fill()
+ // and let the write completion mechanism to kick in.
+ return 0;
+ }
+ }
+ case NEED_UNWRAP:
+ {
+ if (_underFlown)
+ break decryption;
+ continue;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ getEndPoint().close();
+ throw e;
+ }
+ finally
+ {
+ // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+ if (_flushRequiresFillToProgress)
+ {
+ _flushRequiresFillToProgress = false;
+ getExecutor().execute(_runCompletWrite);
+ }
+
+ if (_encryptedInput != null && !_encryptedInput.hasRemaining())
+ {
+ _bufferPool.release(_encryptedInput);
+ _encryptedInput = null;
+ }
+ if (_decryptedInput != null && !_decryptedInput.hasRemaining())
+ {
+ _bufferPool.release(_decryptedInput);
+ _decryptedInput = null;
+ }
+ if (DEBUG)
+ LOG.debug("{} fill exit", SslConnection.this);
+ }
+ }
+
+ private void closeInbound()
+ {
+ try
+ {
+ _sslEngine.closeInbound();
+ }
+ catch (SSLException x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
+ @Override
+ public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
+ {
+ // The contract for flush does not require that all appOuts bytes are written
+ // or even that any appOut bytes are written! If the connection is write block
+ // or busy handshaking, then zero bytes may be taken from appOuts and this method
+ // will return 0 (even if some handshake bytes were flushed and filled).
+ // it is the applications responsibility to call flush again - either in a busy loop
+ // or better yet by using EndPoint#write to do the flushing.
+
+ if (DEBUG)
+ LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
+ int consumed=0;
+ try
+ {
+ if (_cannotAcceptMoreAppDataToFlush)
+ {
+ if (_sslEngine.isOutboundDone())
+ throw new EofException(new ClosedChannelException());
+ return false;
+ }
+
+ // We will need a network buffer
+ if (_encryptedOutput == null)
+ _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+
+ while (true)
+ {
+ // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
+ BufferUtil.compact(_encryptedOutput);
+ int pos = BufferUtil.flipToFill(_encryptedOutput);
+ SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
+ if (DEBUG)
+ LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
+ BufferUtil.flipToFlush(_encryptedOutput, pos);
+ if (wrapResult.bytesConsumed()>0)
+ consumed+=wrapResult.bytesConsumed();
+
+ boolean allConsumed=true;
+ // clear empty buffers to prevent position creeping up the buffer
+ for (ByteBuffer b : appOuts)
+ {
+ if (BufferUtil.isEmpty(b))
+ BufferUtil.clear(b);
+ else
+ allConsumed=false;
+ }
+
+ Status wrapResultStatus = wrapResult.getStatus();
+
+ // and deal with the results returned from the sslEngineWrap
+ switch (wrapResultStatus)
+ {
+ case CLOSED:
+ // The SSL engine has close, but there may be close handshake that needs to be written
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().flush(_encryptedOutput);
+ getEndPoint().shutdownOutput();
+ // If we failed to flush the close handshake then we will just pretend that
+ // the write has progressed normally and let a subsequent call to flush
+ // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+ // The caller will find out about the close on a subsequent flush or fill.
+ if (BufferUtil.hasContent(_encryptedOutput))
+ return false;
+ }
+ // otherwise we have written, and the caller will close the underlying connection
+ else
+ {
+ getEndPoint().shutdownOutput();
+ }
+ return allConsumed;
+
+ case BUFFER_UNDERFLOW:
+ throw new IllegalStateException();
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+ if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
+ }
+
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ shutdownOutput();
+ return allConsumed;
+ }
+
+ // if we have net bytes, let's try to flush them
+ if (BufferUtil.hasContent(_encryptedOutput))
+ getEndPoint().flush(_encryptedOutput);
+
+ // But we also might have more to do for the handshaking state.
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ // Return with the number of bytes consumed (which may be 0)
+ return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+ case NEED_TASK:
+ // run the task and continue
+ _sslEngine.getDelegatedTask().run();
+ continue;
+
+ case NEED_WRAP:
+ // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
+ continue;
+
+ case NEED_UNWRAP:
+ // Ah we need to fill some data so we can write.
+ // So if we were not called from fill and the app is not reading anyway
+ if (appOuts[0]!=__FILL_CALLED_FLUSH && !getFillInterest().isInterested())
+ {
+ // Tell the onFillable method that there might be a write to complete
+ _flushRequiresFillToProgress = true;
+ fill(__FLUSH_CALLED_FILL);
+ // Check if after the fill() we need to wrap again
+ if (handshakeStatus == HandshakeStatus.NEED_WRAP)
+ continue;
+ }
+ return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+ case FINISHED:
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ getEndPoint().close();
+ throw e;
+ }
+ finally
+ {
+ if (DEBUG)
+ LOG.debug("{} flush exit, consumed {}", SslConnection.this, consumed);
+ releaseEncryptedOutputBuffer();
+ }
+ }
+
+ private void releaseEncryptedOutputBuffer()
+ {
+ if (!Thread.holdsLock(DecryptedEndPoint.this))
+ throw new IllegalStateException();
+ if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
+ {
+ _bufferPool.release(_encryptedOutput);
+ _encryptedOutput = null;
+ }
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ boolean ishut = isInputShutdown();
+ boolean oshut = isOutputShutdown();
+ if (DEBUG)
+ LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
+ if (ishut)
+ {
+ // Aggressively close, since inbound close alert has already been processed
+ // and the TLS specification allows to close the connection directly, which
+ // is what most other implementations expect: a FIN rather than a TLS close
+ // reply. If a TLS close reply is sent, most implementations send a RST.
+ getEndPoint().close();
+ }
+ else if (!oshut)
+ {
+ try
+ {
+ _sslEngine.closeOutbound();
+ flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+ SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ getEndPoint().close();
+ }
+ }
+ }
+
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown();
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ // First send the TLS Close Alert, then the FIN
+ shutdownOutput();
+ getEndPoint().close();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return getEndPoint().isOpen();
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return getEndPoint();
+ }
+
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _sslEngine.isInboundDone();
+ }
+
+ @Override
+ public String toString()
+ {
+ return super.toString()+"->"+getEndPoint().toString();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java
new file mode 100644
index 00000000..b393d88c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+public interface SslReconfigurator {
+ public boolean shouldRestartSSL();
+
+ public SSLEngine restartSSL(SSLSession sslSession);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java
new file mode 100644
index 00000000..f8676c3c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty IO : Core SSL Support
+ */
+package org.eclipse.jetty.io.ssl;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java
new file mode 100644
index 00000000..bc049fd5
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java
@@ -0,0 +1,98 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User, Serializable
+{
+ private static final long serialVersionUID = -6290411814232723403L;
+ protected String _method;
+ protected transient UserIdentity _userIdentity;
+
+
+
+ public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+ {
+ _method = method;
+ _userIdentity = userIdentity;
+ }
+
+
+ @Override
+ public String getAuthMethod()
+ {
+ return _method;
+ }
+
+ @Override
+ public UserIdentity getUserIdentity()
+ {
+ return _userIdentity;
+ }
+
+ @Override
+ public boolean isUserInRole(Scope scope, String role)
+ {
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+ if (roleToTest==null)
+ roleToTest=role;
+ //Servlet Spec 3.1 pg 125 if testing special role **
+ if ("**".equals(roleToTest.trim()))
+ {
+ //if ** is NOT a declared role name, the we return true
+ //as the user is authenticated. If ** HAS been declared as a
+ //role name, then we have to check if the user has that role
+ if (!declaredRolesContains("**"))
+ return true;
+ else
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ public boolean declaredRolesContains(String roleName)
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security==null)
+ return false;
+
+ if (security instanceof ConstraintAware)
+ {
+ Set declaredRoles = ((ConstraintAware)security).getRoles();
+ return (declaredRoles != null) && declaredRoles.contains(roleName);
+ }
+
+ return false;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/Authenticator.java b/lib/jetty/org/eclipse/jetty/security/Authenticator.java
new file mode 100644
index 00000000..e36a3b30
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/Authenticator.java
@@ -0,0 +1,138 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Authenticator Interface
+ *
+ * An Authenticator is responsible for checking requests and sending
+ * response challenges in order to authenticate a request.
+ * Various types of {@link Authentication} are returned in order to
+ * signal the next step in authentication.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public interface Authenticator
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Configure the Authenticator
+ * @param configuration
+ */
+ void setConfiguration(AuthConfiguration configuration);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The name of the authentication method
+ */
+ String getAuthMethod();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called prior to validateRequest. The authenticator can
+ * manipulate the request to update it with information that
+ * can be inspected prior to validateRequest being called.
+ * The primary purpose of this method is to satisfy the Servlet
+ * Spec 3.1 section 13.6.3 on handling Form authentication
+ * where the http method of the original request causing authentication
+ * is not the same as the http method resulting from the redirect
+ * after authentication.
+ * @param request
+ */
+ void prepareRequest(ServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ /** Validate a request
+ * @param request The request
+ * @param response The response
+ * @param mandatory True if authentication is mandatory.
+ * @return An Authentication. If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
+ * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
+ * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not manditory, then a
+ * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
+ *
+ * @throws ServerAuthException
+ */
+ Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param request
+ * @param response
+ * @param mandatory
+ * @param validatedUser
+ * @return true if response is secure
+ * @throws ServerAuthException
+ */
+ boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Authenticator Configuration
+ */
+ interface AuthConfiguration
+ {
+ String getAuthMethod();
+ String getRealmName();
+
+ /** Get a SecurityHandler init parameter
+ * @see SecurityHandler#getInitParameter(String)
+ * @param param parameter name
+ * @return Parameter value or null
+ */
+ String getInitParameter(String param);
+
+ /* ------------------------------------------------------------ */
+ /** Get a SecurityHandler init parameter names
+ * @see SecurityHandler#getInitParameterNames()
+ * @return Set of parameter names
+ */
+ Set getInitParameterNames();
+
+ LoginService getLoginService();
+ IdentityService getIdentityService();
+ boolean isSessionRenewedOnAuthentication();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Authenticator Factory
+ */
+ interface Factory
+ {
+ Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java b/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java
new file mode 100644
index 00000000..5cf1348a
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface ConstraintAware
+{
+ List getConstraintMappings();
+ Set getRoles();
+
+ /* ------------------------------------------------------------ */
+ /** Set Constraint Mappings and roles.
+ * Can only be called during initialization.
+ * @param constraintMappings
+ * @param roles
+ */
+ void setConstraintMappings(List constraintMappings, Set roles);
+
+ /* ------------------------------------------------------------ */
+ /** Add a Constraint Mapping.
+ * May be called for running webapplication as an annotated servlet is instantiated.
+ * @param mapping
+ */
+ void addConstraintMapping(ConstraintMapping mapping);
+
+
+ /* ------------------------------------------------------------ */
+ /** Add a Role definition.
+ * May be called on running webapplication as an annotated servlet is instantiated.
+ * @param role
+ */
+ void addRole(String role);
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * When true, requests with http methods not explicitly covered either by inclusion or omissions
+ * in constraints, will have access denied.
+ * @param deny
+ */
+ void setDenyUncoveredHttpMethods(boolean deny);
+
+ boolean isDenyUncoveredHttpMethods();
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * Container must check if there are urls with uncovered http methods
+ */
+ boolean checkPathsWithUncoveredHttpMethods();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java b/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java
new file mode 100644
index 00000000..dd99c5bf
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java
@@ -0,0 +1,100 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.util.security.Constraint;
+
+public class ConstraintMapping
+{
+ String _method;
+ String[] _methodOmissions;
+
+ String _pathSpec;
+
+ Constraint _constraint;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the constraint.
+ */
+ public Constraint getConstraint()
+ {
+ return _constraint;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param constraint The constraint to set.
+ */
+ public void setConstraint(Constraint constraint)
+ {
+ this._constraint = constraint;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the method.
+ */
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param method The method to set.
+ */
+ public void setMethod(String method)
+ {
+ this._method = method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the pathSpec.
+ */
+ public String getPathSpec()
+ {
+ return _pathSpec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec The pathSpec to set.
+ */
+ public void setPathSpec(String pathSpec)
+ {
+ this._pathSpec = pathSpec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param omissions The http-method-omission
+ */
+ public void setMethodOmissions(String[] omissions)
+ {
+ _methodOmissions = omissions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String[] getMethodOmissions()
+ {
+ return _methodOmissions;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java
new file mode 100644
index 00000000..201618d8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -0,0 +1,928 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * ConstraintSecurityHandler
+ *
+ * Handler to enforce SecurityConstraints. This implementation is servlet spec
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
+ * efficiency.
+ *
+ */
+public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
+{
+ private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
+
+ private static final String OMISSION_SUFFIX = ".omission";
+ private static final String ALL_METHODS = "*";
+ private final List _constraintMappings= new CopyOnWriteArrayList<>();
+ private final Set _roles = new CopyOnWriteArraySet<>();
+ private final PathMap> _constraintMap = new PathMap<>();
+ private boolean _denyUncoveredMethods = false;
+
+
+ /* ------------------------------------------------------------ */
+ public static Constraint createConstraint()
+ {
+ return new Constraint();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param constraint
+ */
+ public static Constraint createConstraint(Constraint constraint)
+ {
+ try
+ {
+ return (Constraint)constraint.clone();
+ }
+ catch (CloneNotSupportedException e)
+ {
+ throw new IllegalStateException (e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a security constraint
+ *
+ * @param name
+ * @param authenticate
+ * @param roles
+ * @param dataConstraint
+ */
+ public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
+ {
+ Constraint constraint = createConstraint();
+ if (name != null)
+ constraint.setName(name);
+ constraint.setAuthenticate(authenticate);
+ constraint.setRoles(roles);
+ constraint.setDataConstraint(dataConstraint);
+ return constraint;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param element
+ */
+ public static Constraint createConstraint (String name, HttpConstraintElement element)
+ {
+ return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param rolesAllowed
+ * @param permitOrDeny
+ * @param transport
+ */
+ public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
+ {
+ Constraint constraint = createConstraint();
+
+ if (rolesAllowed == null || rolesAllowed.length==0)
+ {
+ if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
+ {
+ //Equivalent to with no roles
+ constraint.setName(name+"-Deny");
+ constraint.setAuthenticate(true);
+ }
+ else
+ {
+ //Equivalent to no
+ constraint.setName(name+"-Permit");
+ constraint.setAuthenticate(false);
+ }
+ }
+ else
+ {
+ //Equivalent to with list of s
+ constraint.setAuthenticate(true);
+ constraint.setRoles(rolesAllowed);
+ constraint.setName(name+"-RolesAllowed");
+ }
+
+ //Equivalent to //CONFIDENTIAL
+ constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
+ return constraint;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec
+ * @param constraintMappings
+ */
+ public static List getConstraintMappingsForPath(String pathSpec, List constraintMappings)
+ {
+ if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+ return Collections.emptyList();
+
+ List mappings = new ArrayList();
+ for (ConstraintMapping mapping:constraintMappings)
+ {
+ if (pathSpec.equals(mapping.getPathSpec()))
+ {
+ mappings.add(mapping);
+ }
+ }
+ return mappings;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Take out of the constraint mappings those that match the
+ * given path.
+ *
+ * @param pathSpec
+ * @param constraintMappings a new list minus the matching constraints
+ */
+ public static List removeConstraintMappingsForPath(String pathSpec, List constraintMappings)
+ {
+ if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+ return Collections.emptyList();
+
+ List mappings = new ArrayList();
+ for (ConstraintMapping mapping:constraintMappings)
+ {
+ //Remove the matching mappings by only copying in non-matching mappings
+ if (!pathSpec.equals(mapping.getPathSpec()))
+ {
+ mappings.add(mapping);
+ }
+ }
+ return mappings;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
+ *
+ * @param name
+ * @param pathSpec
+ * @param securityElement
+ * @return
+ */
+ public static List createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
+ {
+ List mappings = new ArrayList();
+
+ //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
+ Constraint httpConstraint = null;
+ ConstraintMapping httpConstraintMapping = null;
+
+ if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+ securityElement.getRolesAllowed().length != 0 ||
+ securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+ {
+ httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+
+ //Create a mapping for the pathSpec for the default case
+ httpConstraintMapping = new ConstraintMapping();
+ httpConstraintMapping.setPathSpec(pathSpec);
+ httpConstraintMapping.setConstraint(httpConstraint);
+ mappings.add(httpConstraintMapping);
+ }
+
+
+ //See Spec 13.4.1.2 p127
+ List methodOmissions = new ArrayList();
+
+ //make constraint mappings for this url for each of the HttpMethodConstraintElements
+ Collection methodConstraintElements = securityElement.getHttpMethodConstraints();
+ if (methodConstraintElements != null)
+ {
+ for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
+ {
+ //Make a Constraint that captures the and elements supplied for the HttpMethodConstraintElement
+ Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
+ ConstraintMapping mapping = new ConstraintMapping();
+ mapping.setConstraint(methodConstraint);
+ mapping.setPathSpec(pathSpec);
+ if (methodConstraintElement.getMethodName() != null)
+ {
+ mapping.setMethod(methodConstraintElement.getMethodName());
+ //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+ methodOmissions.add(methodConstraintElement.getMethodName());
+ }
+ mappings.add(mapping);
+ }
+ }
+ //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+ //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+ if (methodOmissions.size() > 0 && httpConstraintMapping != null)
+ httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+
+ return mappings;
+ }
+
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the constraintMappings.
+ */
+ @Override
+ public List getConstraintMappings()
+ {
+ return _constraintMappings;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set getRoles()
+ {
+ return _roles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set, from which the set of known roles
+ * is determined.
+ */
+ public void setConstraintMappings(List constraintMappings)
+ {
+ setConstraintMappings(constraintMappings,null);
+ }
+
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set as array, from which the set of known roles
+ * is determined. Needed to retain API compatibility for 7.x
+ */
+ public void setConstraintMappings( ConstraintMapping[] constraintMappings )
+ {
+ setConstraintMappings( Arrays.asList(constraintMappings), null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set.
+ * @param roles The known roles (or null to determine them from the mappings)
+ */
+ @Override
+ public void setConstraintMappings(List constraintMappings, Set roles)
+ {
+ _constraintMappings.clear();
+ _constraintMappings.addAll(constraintMappings);
+
+ if (roles==null)
+ {
+ roles = new HashSet<>();
+ for (ConstraintMapping cm : constraintMappings)
+ {
+ String[] cmr = cm.getConstraint().getRoles();
+ if (cmr!=null)
+ {
+ for (String r : cmr)
+ if (!ALL_METHODS.equals(r))
+ roles.add(r);
+ }
+ }
+ }
+ setRoles(roles);
+
+ if (isStarted())
+ {
+ for (ConstraintMapping mapping : _constraintMappings)
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the known roles.
+ * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
+ * {@link #setConstraintMappings(List, Set)}.
+ * @param roles The known roles (or null to determine them from the mappings)
+ */
+ public void setRoles(Set roles)
+ {
+ _roles.clear();
+ _roles.addAll(roles);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
+ */
+ @Override
+ public void addConstraintMapping(ConstraintMapping mapping)
+ {
+ _constraintMappings.add(mapping);
+ if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+ {
+ //allow for lazy role naming: if a role is named in a security constraint, try and
+ //add it to the list of declared roles (ie as if it was declared with a security-role
+ for (String role : mapping.getConstraint().getRoles())
+ {
+ if ("*".equals(role) || "**".equals(role))
+ continue;
+ addRole(role);
+ }
+ }
+
+ if (isStarted())
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
+ */
+ @Override
+ public void addRole(String role)
+ {
+ //add to list of declared roles
+ boolean modified = _roles.add(role);
+ if (isStarted() && modified)
+ {
+ // Add the new role to currently defined any role role infos
+ for (Map map : _constraintMap.values())
+ {
+ for (RoleInfo info : map.values())
+ {
+ if (info.isAnyRole())
+ info.addRole(role);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.SecurityHandler#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ _constraintMap.clear();
+ if (_constraintMappings!=null)
+ {
+ for (ConstraintMapping mapping : _constraintMappings)
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+
+ //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+ checkPathsWithUncoveredHttpMethods();
+
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ _constraintMap.clear();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create and combine the constraint with the existing processed
+ * constraints.
+ *
+ * @param mapping
+ */
+ protected void processConstraintMapping(ConstraintMapping mapping)
+ {
+ Map mappings = _constraintMap.get(mapping.getPathSpec());
+ if (mappings == null)
+ {
+ mappings = new HashMap();
+ _constraintMap.put(mapping.getPathSpec(),mappings);
+ }
+ RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
+ if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
+ return;
+
+ if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
+ {
+ processConstraintMappingWithMethodOmissions(mapping, mappings);
+ return;
+ }
+
+ String httpMethod = mapping.getMethod();
+ if (httpMethod==null)
+ httpMethod=ALL_METHODS;
+ RoleInfo roleInfo = mappings.get(httpMethod);
+ if (roleInfo == null)
+ {
+ roleInfo = new RoleInfo();
+ mappings.put(httpMethod,roleInfo);
+ if (allMethodsRoleInfo != null)
+ {
+ roleInfo.combine(allMethodsRoleInfo);
+ }
+ }
+ if (roleInfo.isForbidden())
+ return;
+
+ //add in info from the constraint
+ configureRoleInfo(roleInfo, mapping);
+
+ if (roleInfo.isForbidden())
+ {
+ if (httpMethod.equals(ALL_METHODS))
+ {
+ mappings.clear();
+ mappings.put(ALL_METHODS,roleInfo);
+ }
+ }
+ else
+ {
+ //combine with any entry that covers all methods
+ if (httpMethod == null)
+ {
+ for (Map.Entry entry : mappings.entrySet())
+ {
+ if (entry.getKey() != null)
+ {
+ RoleInfo specific = entry.getValue();
+ specific.combine(roleInfo);
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constraints that name method omissions are dealt with differently.
+ * We create an entry in the mappings with key "<method>.omission". This entry
+ * is only ever combined with other omissions for the same method to produce a
+ * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
+ * a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
+ * the mappings: an entry that names the method of the Request specifically, an
+ * entry that names constraints that apply to all methods, entries of the form
+ * <method>.omission, where the method of the Request is not named in the omission.
+ * @param mapping
+ * @param mappings
+ */
+ protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map mappings)
+ {
+ String[] omissions = mapping.getMethodOmissions();
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i 0)
+ sb.append(".");
+ sb.append(omissions[i]);
+ }
+ sb.append(OMISSION_SUFFIX);
+ RoleInfo ri = new RoleInfo();
+ mappings.put(sb.toString(), ri);
+ configureRoleInfo(ri, mapping);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Initialize or update the RoleInfo from the constraint
+ * @param ri
+ * @param mapping
+ */
+ protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
+ {
+ Constraint constraint = mapping.getConstraint();
+ boolean forbidden = constraint.isForbidden();
+ ri.setForbidden(forbidden);
+
+ //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
+ //which we need in order to do combining of omissions in prepareConstraintInfo
+ UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
+ ri.setUserDataConstraint(userDataConstraint);
+
+ //if forbidden, no point setting up roles
+ if (!ri.isForbidden())
+ {
+ //add in the roles
+ boolean checked = mapping.getConstraint().getAuthenticate();
+ ri.setChecked(checked);
+
+ if (ri.isChecked())
+ {
+ if (mapping.getConstraint().isAnyRole())
+ {
+ // * means matches any defined role
+ for (String role : _roles)
+ ri.addRole(role);
+ ri.setAnyRole(true);
+ }
+ else if (mapping.getConstraint().isAnyAuth())
+ {
+ //being authenticated is sufficient, not necessary to check roles
+ ri.setAnyAuth(true);
+ }
+ else
+ {
+ //user must be in one of the named roles
+ String[] newRoles = mapping.getConstraint().getRoles();
+ for (String role : newRoles)
+ {
+ //check role has been defined
+ if (!_roles.contains(role))
+ throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+ ri.addRole(role);
+ }
+ }
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Find constraints that apply to the given path.
+ * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
+ * represents a merged set of user data constraints, roles etc -:
+ *
+ * A mapping of an exact method name
+ * A mapping with key * that matches every method name
+ * Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given
+ *
+ *
+ * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
+ */
+ @Override
+ protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
+ {
+ Map mappings = (Map)_constraintMap.match(pathInContext);
+
+ if (mappings != null)
+ {
+ String httpMethod = request.getMethod();
+ RoleInfo roleInfo = mappings.get(httpMethod);
+ if (roleInfo == null)
+ {
+ //No specific http-method names matched
+ List applicableConstraints = new ArrayList();
+
+ //Get info for constraint that matches all methods if it exists
+ RoleInfo all = mappings.get(ALL_METHODS);
+ if (all != null)
+ applicableConstraints.add(all);
+
+
+ //Get info for constraints that name method omissions where target method name is not omitted
+ //(ie matches because target method is not omitted, hence considered covered by the constraint)
+ for (Entry entry: mappings.entrySet())
+ {
+ if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
+ applicableConstraints.add(entry.getValue());
+ }
+
+ if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+ {
+ roleInfo = new RoleInfo();
+ roleInfo.setForbidden(true);
+ }
+ else if (applicableConstraints.size() == 1)
+ roleInfo = applicableConstraints.get(0);
+ else
+ {
+ roleInfo = new RoleInfo();
+ roleInfo.setUserDataConstraint(UserDataConstraint.None);
+
+ for (RoleInfo r:applicableConstraints)
+ roleInfo.combine(r);
+ }
+
+ }
+
+ return roleInfo;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
+ {
+ if (roleInfo == null)
+ return true;
+
+ if (roleInfo.isForbidden())
+ return false;
+
+ UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
+ if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
+ return true;
+
+ HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
+
+ if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
+ {
+ if (request.isSecure())
+ return true;
+
+ if (httpConfig.getSecurePort() > 0)
+ {
+ String scheme = httpConfig.getSecureScheme();
+ int port = httpConfig.getSecurePort();
+ String url = ("https".equalsIgnoreCase(scheme) && port==443)
+ ? "https://"+request.getServerName()+request.getRequestURI()
+ : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
+ if (request.getQueryString() != null)
+ url += "?" + request.getQueryString();
+ response.setContentLength(0);
+ response.sendRedirect(url);
+ }
+ else
+ response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
+
+ request.setHandled(true);
+ return false;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
+ }
+
+ }
+
+ @Override
+ protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
+ {
+ return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
+ */
+ @Override
+ protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
+ throws IOException
+ {
+ if (constraintInfo == null)
+ {
+ return true;
+ }
+ RoleInfo roleInfo = (RoleInfo)constraintInfo;
+
+ if (!roleInfo.isChecked())
+ {
+ return true;
+ }
+
+ //handle ** role constraint
+ if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
+ {
+ return true;
+ }
+
+ //check if user is any of the allowed roles
+ boolean isUserInRole = false;
+ for (String role : roleInfo.getRoles())
+ {
+ if (userIdentity.isUserInRole(role, null))
+ {
+ isUserInRole = true;
+ break;
+ }
+ }
+
+ //handle * role constraint
+ if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+ {
+ return true;
+ }
+
+ //normal role check
+ if (isUserInRole)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out,String indent) throws IOException
+ {
+ // TODO these should all be beans
+ dumpBeans(out,indent,
+ Collections.singleton(getLoginService()),
+ Collections.singleton(getIdentityService()),
+ Collections.singleton(getAuthenticator()),
+ Collections.singleton(_roles),
+ _constraintMap.entrySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+ */
+ @Override
+ public void setDenyUncoveredHttpMethods(boolean deny)
+ {
+ _denyUncoveredMethods = deny;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isDenyUncoveredHttpMethods()
+ {
+ return _denyUncoveredMethods;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ */
+ @Override
+ public boolean checkPathsWithUncoveredHttpMethods()
+ {
+ Set paths = getPathsWithUncoveredHttpMethods();
+ if (paths != null && !paths.isEmpty())
+ {
+ for (String p:paths)
+ LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
+ if (LOG.isDebugEnabled())
+ LOG.debug(new Throwable());
+ return true;
+ }
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ * The container must check all the combined security constraint
+ * information and log any methods that are not protected and the
+ * urls at which they are not protected
+ *
+ * @return list of paths for which there are uncovered methods
+ */
+ public Set getPathsWithUncoveredHttpMethods ()
+ {
+ //if automatically denying uncovered methods, there are no uncovered methods
+ if (_denyUncoveredMethods)
+ return Collections.emptySet();
+
+ Set uncoveredPaths = new HashSet();
+
+ for (String path:_constraintMap.keySet())
+ {
+ Map methodMappings = _constraintMap.get(path);
+ //Each key is either:
+ // : an exact method name
+ // : * which means that the constraint applies to every method
+ // : a name of the form ...omission, which means it applies to every method EXCEPT those named
+ if (methodMappings.get(ALL_METHODS) != null)
+ continue; //can't be any uncovered methods for this url path
+
+ boolean hasOmissions = omissionsExist(path, methodMappings);
+
+ for (String method:methodMappings.keySet())
+ {
+ if (method.endsWith(OMISSION_SUFFIX))
+ {
+ Set omittedMethods = getOmittedMethods(method);
+ for (String m:omittedMethods)
+ {
+ if (!methodMappings.containsKey(m))
+ uncoveredPaths.add(path);
+ }
+ }
+ else
+ {
+ //an exact method name
+ if (!hasOmissions)
+ //a http-method does not have http-method-omission to cover the other method names
+ uncoveredPaths.add(path);
+ }
+
+ }
+ }
+ return uncoveredPaths;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check if any http method omissions exist in the list of method
+ * to auth info mappings.
+ *
+ * @param path
+ * @param methodMappings
+ * @return
+ */
+ protected boolean omissionsExist (String path, Map methodMappings)
+ {
+ if (methodMappings == null)
+ return false;
+ boolean hasOmissions = false;
+ for (String m:methodMappings.keySet())
+ {
+ if (m.endsWith(OMISSION_SUFFIX))
+ hasOmissions = true;
+ }
+ return hasOmissions;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Given a string of the form <method>.<method>.omission
+ * split out the individual method names.
+ *
+ * @param omission
+ * @return
+ */
+ protected Set getOmittedMethods (String omission)
+ {
+ if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+ return Collections.emptySet();
+
+ String[] strings = omission.split("\\.");
+ Set methods = new HashSet();
+ for (int i=0;i
+{
+
+ T fetch(HttpServletRequest request);
+
+ void store(T data, HttpServletResponse response);
+
+ void clear(HttpServletRequest request);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
new file mode 100644
index 00000000..534a6d40
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
@@ -0,0 +1,96 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * The Default Authenticator Factory.
+ * Uses the {@link AuthConfiguration#getAuthMethod()} to select an {@link Authenticator} from:
+ * {@link org.eclipse.jetty.security.authentication.BasicAuthenticator}
+ * {@link org.eclipse.jetty.security.authentication.DigestAuthenticator}
+ * {@link org.eclipse.jetty.security.authentication.FormAuthenticator}
+ * {@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}
+ *
+ * All authenticators derived from {@link org.eclipse.jetty.security.authentication.LoginAuthenticator} are
+ * wrapped with a {@link org.eclipse.jetty.security.authentication.DeferredAuthentication}
+ * instance, which is used if authentication is not mandatory.
+ *
+ * The Authentications from the {@link org.eclipse.jetty.security.authentication.FormAuthenticator} are always wrapped in a
+ * {@link org.eclipse.jetty.security.authentication.SessionAuthentication}
+ *
+ * If a {@link LoginService} has not been set on this factory, then
+ * the service is selected by searching the {@link Server#getBeans(Class)} results for
+ * a service that matches the realm name, else the first LoginService found is used.
+ *
+ */
+public class DefaultAuthenticatorFactory implements Authenticator.Factory
+{
+ LoginService _loginService;
+
+ public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
+ {
+ String auth=configuration.getAuthMethod();
+ Authenticator authenticator=null;
+
+ if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
+ authenticator=new BasicAuthenticator();
+ else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
+ authenticator=new DigestAuthenticator();
+ else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
+ authenticator=new FormAuthenticator();
+ else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
+ authenticator = new SpnegoAuthenticator();
+ else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
+ authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
+ if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
+ authenticator=new ClientCertAuthenticator();
+
+ return authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the loginService
+ */
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param loginService the loginService to set
+ */
+ public void setLoginService(LoginService loginService)
+ {
+ _loginService = loginService;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java b/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java
new file mode 100644
index 00000000..c8ae0c29
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java
@@ -0,0 +1,90 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Default Identity Service implementation.
+ * This service handles only role reference maps passed in an
+ * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}. If there are roles
+ * refs present, then associate will wrap the UserIdentity with one
+ * that uses the role references in the
+ * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ * implementation. All other operations are effectively noops.
+ *
+ */
+public class DefaultIdentityService implements IdentityService
+{
+ /* ------------------------------------------------------------ */
+ public DefaultIdentityService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * If there are roles refs present in the scope, then wrap the UserIdentity
+ * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ */
+ public Object associate(UserIdentity user)
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void disassociate(Object previous)
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object setRunAs(UserIdentity user, RunAsToken token)
+ {
+ return token;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void unsetRunAs(Object lastToken)
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public RunAsToken newRunAsToken(String runAsName)
+ {
+ return new RoleRunAsToken(runAsName);
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getSystemUserIdentity()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles)
+ {
+ return new DefaultUserIdentity(subject,userPrincipal,roles);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java
new file mode 100644
index 00000000..65e083d5
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java
@@ -0,0 +1,81 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The default implementation of UserIdentity.
+ *
+ */
+public class DefaultUserIdentity implements UserIdentity
+{
+ private final Subject _subject;
+ private final Principal _userPrincipal;
+ private final String[] _roles;
+
+ public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
+ {
+ _subject=subject;
+ _userPrincipal=userPrincipal;
+ _roles=roles;
+ }
+
+ public Subject getSubject()
+ {
+ return _subject;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return _userPrincipal;
+ }
+
+ public boolean isUserInRole(String role, Scope scope)
+ {
+ //Servlet Spec 3.1, pg 125
+ if ("*".equals(role))
+ return false;
+
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+
+ //Servlet Spec 3.1, pg 125
+ if (roleToTest == null)
+ roleToTest = role;
+
+ for (String r :_roles)
+ if (r.equals(roleToTest))
+ return true;
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return DefaultUserIdentity.class.getSimpleName()+"('"+_userPrincipal+"')";
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
new file mode 100644
index 00000000..8499a609
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
@@ -0,0 +1,99 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ */
+public class HashCrossContextPsuedoSession implements CrossContextPsuedoSession
+{
+ private final String _cookieName;
+
+ private final String _cookiePath;
+
+ private final Random _random = new SecureRandom();
+
+ private final Map _data = new HashMap();
+
+ public HashCrossContextPsuedoSession(String cookieName, String cookiePath)
+ {
+ this._cookieName = cookieName;
+ this._cookiePath = cookiePath == null ? "/" : cookiePath;
+ }
+
+ public T fetch(HttpServletRequest request)
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null)
+ return null;
+
+ for (Cookie cookie : cookies)
+ {
+ if (_cookieName.equals(cookie.getName()))
+ {
+ String key = cookie.getValue();
+ return _data.get(key);
+ }
+ }
+ return null;
+ }
+
+ public void store(T datum, HttpServletResponse response)
+ {
+ String key;
+
+ synchronized (_data)
+ {
+ // Create new ID
+ while (true)
+ {
+ key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7));
+ if (!_data.containsKey(key)) break;
+ }
+
+ _data.put(key, datum);
+ }
+
+ Cookie cookie = new Cookie(_cookieName, key);
+ cookie.setPath(_cookiePath);
+ response.addCookie(cookie);
+ }
+
+ public void clear(HttpServletRequest request)
+ {
+ for (Cookie cookie : request.getCookies())
+ {
+ if (_cookieName.equals(cookie.getName()))
+ {
+ String key = cookie.getValue();
+ _data.remove(key);
+ break;
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashLoginService.java b/lib/jetty/org/eclipse/jetty/security/HashLoginService.java
new file mode 100644
index 00000000..335aabd7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/HashLoginService.java
@@ -0,0 +1,184 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.security.PropertyUserStore.UserListener;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * Properties User Realm.
+ *
+ * An implementation of UserRealm that stores users and roles in-memory in HashMaps.
+ *
+ * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
+ *
+ *
+ * username: password [,rolename ...]
+ *
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class HashLoginService extends MappedLoginService implements UserListener
+{
+ private static final Logger LOG = Log.getLogger(HashLoginService.class);
+
+ private PropertyUserStore _propertyUserStore;
+ private String _config;
+ private Resource _configResource;
+ private Scanner _scanner;
+ private int _refreshInterval = 0;// default is not to reload
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService(String name)
+ {
+ setName(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService(String name, String config)
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void getConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Resource getConfigResource()
+ {
+ return _configResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
+ *
+ * @param config
+ * Filename or url of user properties file.
+ */
+ public void setConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRefreshInterval(int msec)
+ {
+ _refreshInterval = msec;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRefreshInterval()
+ {
+ return _refreshInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected UserIdentity loadUser(String username)
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void loadUsers() throws IOException
+ {
+ // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (_propertyUserStore == null)
+ {
+ if(LOG.isDebugEnabled())
+ LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval);
+
+ _propertyUserStore = new PropertyUserStore();
+ _propertyUserStore.setRefreshInterval(_refreshInterval);
+ _propertyUserStore.setConfig(_config);
+ _propertyUserStore.registerUserListener(this);
+ _propertyUserStore.start();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_scanner != null)
+ _scanner.stop();
+ _scanner = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void update(String userName, Credential credential, String[] roleArray)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("update: " + userName + " Roles: " + roleArray.length);
+ putUser(userName,credential,roleArray);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void remove(String userName)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("remove: " + userName);
+ removeUser(userName);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/IdentityService.java b/lib/jetty/org/eclipse/jetty/security/IdentityService.java
new file mode 100644
index 00000000..99094c17
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/IdentityService.java
@@ -0,0 +1,95 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+
+/* ------------------------------------------------------------ */
+/**
+ * Associates UserIdentities from with threads and UserIdentity.Contexts.
+ *
+ */
+public interface IdentityService
+{
+ final static String[] NO_ROLES = new String[]{};
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Associate a user identity with the current thread.
+ * This is called with as a thread enters the
+ * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method and then again with a null argument as that call exits.
+ * @param user The current user or null for no user to associated.
+ * @return an object representing the previous associated state
+ */
+ Object associate(UserIdentity user);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Disassociate the user identity from the current thread
+ * and restore previous identity.
+ * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
+ */
+ void disassociate(Object previous);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Associate a runas Token with the current user and thread.
+ * @param user The UserIdentity
+ * @param token The runAsToken to associate.
+ * @return The previous runAsToken or null.
+ */
+ Object setRunAs(UserIdentity user, RunAsToken token);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Disassociate the current runAsToken from the thread
+ * and reassociate the previous token.
+ * @param token RUNAS returned from previous associateRunAs call
+ */
+ void unsetRunAs(Object token);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new UserIdentity for use with this identity service.
+ * The UserIdentity should be immutable and able to be cached.
+ *
+ * @param subject Subject to include in UserIdentity
+ * @param userPrincipal Principal to include in UserIdentity. This will be returned from getUserPrincipal calls
+ * @param roles set of roles to include in UserIdentity.
+ * @return A new immutable UserIdententity
+ */
+ UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new RunAsToken from a runAsName (normally a role).
+ * @param runAsName Normally a role name
+ * @return A new immutable RunAsToken
+ */
+ RunAsToken newRunAsToken(String runAsName);
+
+ /* ------------------------------------------------------------ */
+ UserIdentity getSystemUserIdentity();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java b/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java
new file mode 100644
index 00000000..30b0a831
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java
@@ -0,0 +1,297 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
+ * HashULoginService and adds a method to fetch user information from database.
+ * The login() method checks the inherited Map for the user. If the user is not
+ * found, it will fetch details from the database and populate the inherited
+ * Map. It then calls the superclass login() method to perform the actual
+ * authentication. Periodically (controlled by configuration parameter),
+ * internal hashes are cleared. Caching can be disabled by setting cache refresh
+ * interval to zero. Uses one database connection that is initialized at
+ * startup. Reconnect on failures. authenticate() is 'synchronized'.
+ *
+ * An example properties file for configuration is in
+ * $JETTY_HOME/etc/jdbcRealm.properties
+ *
+ * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
+ *
+ *
+ *
+ *
+ */
+
+public class JDBCLoginService extends MappedLoginService
+{
+ private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
+
+ protected String _config;
+ protected String _jdbcDriver;
+ protected String _url;
+ protected String _userName;
+ protected String _password;
+ protected String _userTableKey;
+ protected String _userTablePasswordField;
+ protected String _roleTableRoleField;
+ protected int _cacheTime;
+ protected long _lastHashPurge;
+ protected Connection _con;
+ protected String _userSql;
+ protected String _roleSql;
+
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService()
+ throws IOException
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name)
+ throws IOException
+ {
+ setName(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name, String config)
+ throws IOException
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name, IdentityService identityService, String config)
+ throws IOException
+ {
+ setName(name);
+ setIdentityService(identityService);
+ setConfig(config);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.MappedLoginService#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ Properties properties = new Properties();
+ Resource resource = Resource.newResource(_config);
+ try (InputStream in = resource.getInputStream())
+ {
+ properties.load(in);
+ }
+ _jdbcDriver = properties.getProperty("jdbcdriver");
+ _url = properties.getProperty("url");
+ _userName = properties.getProperty("username");
+ _password = properties.getProperty("password");
+ String _userTable = properties.getProperty("usertable");
+ _userTableKey = properties.getProperty("usertablekey");
+ String _userTableUserField = properties.getProperty("usertableuserfield");
+ _userTablePasswordField = properties.getProperty("usertablepasswordfield");
+ String _roleTable = properties.getProperty("roletable");
+ String _roleTableKey = properties.getProperty("roletablekey");
+ _roleTableRoleField = properties.getProperty("roletablerolefield");
+ String _userRoleTable = properties.getProperty("userroletable");
+ String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
+ String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
+ _cacheTime = new Integer(properties.getProperty("cachetime"));
+
+ if (_jdbcDriver == null || _jdbcDriver.equals("")
+ || _url == null
+ || _url.equals("")
+ || _userName == null
+ || _userName.equals("")
+ || _password == null
+ || _cacheTime < 0)
+ {
+ LOG.warn("UserRealm " + getName() + " has not been properly configured");
+ }
+ _cacheTime *= 1000;
+ _lastHashPurge = 0;
+ _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
+ _roleSql = "select r." + _roleTableRoleField
+ + " from "
+ + _roleTable
+ + " r, "
+ + _userRoleTable
+ + " u where u."
+ + _userRoleTableUserKey
+ + " = ?"
+ + " and r."
+ + _roleTableKey
+ + " = u."
+ + _userRoleTableRoleKey;
+
+ Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Load JDBC connection configuration from properties file.
+ *
+ * @param config Filename or url of user properties file.
+ */
+ public void setConfig(String config)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _config=config;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * (re)Connect to database with parameters setup by loadConfig()
+ */
+ public void connectDatabase()
+ {
+ try
+ {
+ Class.forName(_jdbcDriver);
+ _con = DriverManager.getConnection(_url, _userName, _password);
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public UserIdentity login(String username, Object credentials)
+ {
+ long now = System.currentTimeMillis();
+ if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
+ {
+ _users.clear();
+ _lastHashPurge = now;
+ closeConnection();
+ }
+
+ return super.login(username,credentials);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void loadUsers()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected UserIdentity loadUser(String username)
+ {
+ try
+ {
+ if (null == _con)
+ connectDatabase();
+
+ if (null == _con)
+ throw new SQLException("Can't connect to database");
+
+ try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
+ {
+ stat1.setObject(1, username);
+ try (ResultSet rs1 = stat1.executeQuery())
+ {
+ if (rs1.next())
+ {
+ int key = rs1.getInt(_userTableKey);
+ String credentials = rs1.getString(_userTablePasswordField);
+ List roles = new ArrayList();
+
+ try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+ {
+ stat2.setInt(1, key);
+ try (ResultSet rs2 = stat2.executeQuery())
+ {
+ while (rs2.next())
+ roles.add(rs2.getString(_roleTableRoleField));
+ }
+ }
+ return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+ }
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+ closeConnection();
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected UserIdentity putUser (String username, String credentials, String[] roles)
+ {
+ return putUser(username, Credential.getCredential(credentials),roles);
+ }
+
+
+ /**
+ * Close an existing connection
+ */
+ private void closeConnection ()
+ {
+ if (_con != null)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
+ try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
+ }
+ _con = null;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/LoginService.java b/lib/jetty/org/eclipse/jetty/security/LoginService.java
new file mode 100644
index 00000000..1e64141f
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/LoginService.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Login Service Interface.
+ *
+ * The Login service provides an abstract mechanism for an {@link Authenticator}
+ * to check credentials and to create a {@link UserIdentity} using the
+ * set {@link IdentityService}.
+ */
+public interface LoginService
+{
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the name of the login service (aka Realm name)
+ */
+ String getName();
+
+ /* ------------------------------------------------------------ */
+ /** Login a user.
+ * @param username The user name
+ * @param credentials The users credentials
+ * @return A UserIdentity if the credentials matched, otherwise null
+ */
+ UserIdentity login(String username,Object credentials);
+
+ /* ------------------------------------------------------------ */
+ /** Validate a user identity.
+ * Validate that a UserIdentity previously created by a call
+ * to {@link #login(String, Object)} is still valid.
+ * @param user The user to validate
+ * @return true if authentication has not been revoked for the user.
+ */
+ boolean validate(UserIdentity user);
+
+ /* ------------------------------------------------------------ */
+ /** Get the IdentityService associated with this Login Service.
+ * @return the IdentityService associated with this Login Service.
+ */
+ IdentityService getIdentityService();
+
+ /* ------------------------------------------------------------ */
+ /** Set the IdentityService associated with this Login Service.
+ * @param service the IdentityService associated with this Login Service.
+ */
+ void setIdentityService(IdentityService service);
+
+ void logout(UserIdentity user);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java b/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java
new file mode 100644
index 00000000..480131de
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java
@@ -0,0 +1,343 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A login service that keeps UserIdentities in a concurrent map
+ * either as the source or a cache of the users.
+ *
+ */
+public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
+{
+ private static final Logger LOG = Log.getLogger(MappedLoginService.class);
+
+ protected IdentityService _identityService=new DefaultIdentityService();
+ protected String _name;
+ protected final ConcurrentMap _users=new ConcurrentHashMap();
+
+ /* ------------------------------------------------------------ */
+ protected MappedLoginService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the name.
+ * @return the name
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the identityService.
+ * @return the identityService
+ */
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the users.
+ * @return the users
+ */
+ public ConcurrentMap getUsers()
+ {
+ return _users;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the identityService.
+ * @param identityService the identityService to set
+ */
+ public void setIdentityService(IdentityService identityService)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _identityService = identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the name.
+ * @param name the name to set
+ */
+ public void setName(String name)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _name = name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the users.
+ * @param users the users to set
+ */
+ public void setUsers(Map users)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _users.clear();
+ _users.putAll(users);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ loadUsers();
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void logout(UserIdentity identity)
+ {
+ LOG.debug("logout {}",identity);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName()+"["+_name+"]";
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put user into realm.
+ * Called by implementations to put the user data loaded from
+ * file/db etc into the user structure.
+ * @param userName User name
+ * @param info a UserIdentity instance, or a String password or Credential instance
+ * @return User instance
+ */
+ protected synchronized UserIdentity putUser(String userName, Object info)
+ {
+ final UserIdentity identity;
+ if (info instanceof UserIdentity)
+ identity=(UserIdentity)info;
+ else
+ {
+ Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
+
+ Principal userPrincipal = new KnownUser(userName,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+ subject.setReadOnly();
+ identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
+ }
+
+ _users.put(userName,identity);
+ return identity;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put user into realm.
+ * @param userName The user to add
+ * @param credential The users Credentials
+ * @param roles The users roles
+ * @return UserIdentity
+ */
+ public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
+ {
+ Principal userPrincipal = new KnownUser(userName,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+
+ if (roles!=null)
+ for (String role : roles)
+ subject.getPrincipals().add(new RolePrincipal(role));
+
+ subject.setReadOnly();
+ UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
+ _users.put(userName,identity);
+ return identity;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeUser(String username)
+ {
+ _users.remove(username);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object)
+ */
+ public UserIdentity login(String username, Object credentials)
+ {
+ if (username == null)
+ return null;
+
+ UserIdentity user = _users.get(username);
+
+ if (user==null)
+ user = loadUser(username);
+
+ if (user!=null)
+ {
+ UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
+ if (principal.authenticate(credentials))
+ return user;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean validate(UserIdentity user)
+ {
+ if (_users.containsKey(user.getUserPrincipal().getName()))
+ return true;
+
+ if (loadUser(user.getUserPrincipal().getName())!=null)
+ return true;
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract UserIdentity loadUser(String username);
+
+ /* ------------------------------------------------------------ */
+ protected abstract void loadUsers() throws IOException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public interface UserPrincipal extends Principal,Serializable
+ {
+ boolean authenticate(Object credentials);
+ public boolean isAuthenticated();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class RolePrincipal implements Principal,Serializable
+ {
+ private static final long serialVersionUID = 2998397924051854402L;
+ private final String _roleName;
+ public RolePrincipal(String name)
+ {
+ _roleName=name;
+ }
+ public String getName()
+ {
+ return _roleName;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Anonymous implements UserPrincipal,Serializable
+ {
+ private static final long serialVersionUID = 1097640442553284845L;
+
+ public boolean isAuthenticated()
+ {
+ return false;
+ }
+
+ public String getName()
+ {
+ return "Anonymous";
+ }
+
+ public boolean authenticate(Object credentials)
+ {
+ return false;
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class KnownUser implements UserPrincipal,Serializable
+ {
+ private static final long serialVersionUID = -6226920753748399662L;
+ private final String _name;
+ private final Credential _credential;
+
+ /* -------------------------------------------------------- */
+ public KnownUser(String name,Credential credential)
+ {
+ _name=name;
+ _credential=credential;
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean authenticate(Object credentials)
+ {
+ return _credential!=null && _credential.check(credentials);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean isAuthenticated()
+ {
+ return true;
+ }
+
+ /* -------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+ }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java b/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java
new file mode 100644
index 00000000..7a8b5e75
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java
@@ -0,0 +1,356 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.MappedLoginService.KnownUser;
+import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyUserStore
+ *
+ * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
+ *
+ *
+ * username: password [,rolename ...]
+ *
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
+
+ private String _config;
+ private Resource _configResource;
+ private Scanner _scanner;
+ private int _refreshInterval = 0;// default is not to reload
+
+ private IdentityService _identityService = new DefaultIdentityService();
+ private boolean _firstLoad = true; // true if first load, false from that point on
+ private final List _knownUsers = new ArrayList();
+ private final Map _knownUserIdentities = new HashMap();
+ private List _listeners;
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getUserIdentity(String userName)
+ {
+ return _knownUserIdentities.get(userName);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * returns the resource associated with the configured properties file, creating it if necessary
+ */
+ public Resource getConfigResource() throws IOException
+ {
+ if (_configResource == null)
+ {
+ _configResource = Resource.newResource(_config);
+ }
+
+ return _configResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * sets the refresh interval (in seconds)
+ */
+ public void setRefreshInterval(int msec)
+ {
+ _refreshInterval = msec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * refresh interval in seconds for how often the properties file should be checked for changes
+ */
+ public int getRefreshInterval()
+ {
+ return _refreshInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void loadUsers() throws IOException
+ {
+ if (_config == null)
+ return;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Load " + this + " from " + _config);
+ Properties properties = new Properties();
+ if (getConfigResource().exists())
+ properties.load(getConfigResource().getInputStream());
+ Set known = new HashSet();
+
+ for (Map.Entry entry : properties.entrySet())
+ {
+ String username = ((String)entry.getKey()).trim();
+ String credentials = ((String)entry.getValue()).trim();
+ String roles = null;
+ int c = credentials.indexOf(',');
+ if (c > 0)
+ {
+ roles = credentials.substring(c + 1).trim();
+ credentials = credentials.substring(0,c).trim();
+ }
+
+ if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+ {
+ String[] roleArray = IdentityService.NO_ROLES;
+ if (roles != null && roles.length() > 0)
+ {
+ roleArray = roles.split(",");
+ }
+ known.add(username);
+ Credential credential = Credential.getCredential(credentials);
+
+ Principal userPrincipal = new KnownUser(username,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+
+ if (roles != null)
+ {
+ for (String role : roleArray)
+ {
+ subject.getPrincipals().add(new RolePrincipal(role));
+ }
+ }
+
+ subject.setReadOnly();
+
+ _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
+ notifyUpdate(username,credential,roleArray);
+ }
+ }
+
+ synchronized (_knownUsers)
+ {
+ /*
+ * if its not the initial load then we want to process removed users
+ */
+ if (!_firstLoad)
+ {
+ Iterator users = _knownUsers.iterator();
+ while (users.hasNext())
+ {
+ String user = users.next();
+ if (!known.contains(user))
+ {
+ _knownUserIdentities.remove(user);
+ notifyRemove(user);
+ }
+ }
+ }
+
+ /*
+ * reset the tracked _users list to the known users we just processed
+ */
+
+ _knownUsers.clear();
+ _knownUsers.addAll(known);
+
+ }
+
+ /*
+ * set initial load to false as there should be no more initial loads
+ */
+ _firstLoad = false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
+ * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
+ *
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (getRefreshInterval() > 0)
+ {
+ _scanner = new Scanner();
+ _scanner.setScanInterval(getRefreshInterval());
+ List dirList = new ArrayList(1);
+ dirList.add(getConfigResource().getFile().getParentFile());
+ _scanner.setScanDirs(dirList);
+ _scanner.setFilenameFilter(new FilenameFilter()
+ {
+ public boolean accept(File dir, String name)
+ {
+ File f = new File(dir,name);
+ try
+ {
+ if (f.compareTo(getConfigResource().getFile()) == 0)
+ {
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+
+ return false;
+ }
+
+ });
+
+ _scanner.addListener(new BulkListener()
+ {
+ public void filesChanged(List filenames) throws Exception
+ {
+ if (filenames == null)
+ return;
+ if (filenames.isEmpty())
+ return;
+ if (filenames.size() == 1)
+ {
+ Resource r = Resource.newResource(filenames.get(0));
+ if (r.getFile().equals(_configResource.getFile()))
+ loadUsers();
+ }
+ }
+
+ public String toString()
+ {
+ return "PropertyUserStore$Scanner";
+ }
+
+ });
+
+ _scanner.setReportExistingFilesOnStartup(true);
+ _scanner.setRecursive(false);
+ _scanner.start();
+ }
+ else
+ {
+ loadUsers();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_scanner != null)
+ _scanner.stop();
+ _scanner = null;
+ }
+
+ /**
+ * Notifies the registered listeners of potential updates to a user
+ *
+ * @param username
+ * @param credential
+ * @param roleArray
+ */
+ private void notifyUpdate(String username, Credential credential, String[] roleArray)
+ {
+ if (_listeners != null)
+ {
+ for (Iterator i = _listeners.iterator(); i.hasNext();)
+ {
+ i.next().update(username,credential,roleArray);
+ }
+ }
+ }
+
+ /**
+ * notifies the registered listeners that a user has been removed.
+ *
+ * @param username
+ */
+ private void notifyRemove(String username)
+ {
+ if (_listeners != null)
+ {
+ for (Iterator i = _listeners.iterator(); i.hasNext();)
+ {
+ i.next().remove(username);
+ }
+ }
+ }
+
+ /**
+ * registers a listener to be notified of the contents of the property file
+ */
+ public void registerUserListener(UserListener listener)
+ {
+ if (_listeners == null)
+ {
+ _listeners = new ArrayList();
+ }
+ _listeners.add(listener);
+ }
+
+ /**
+ * UserListener
+ */
+ public interface UserListener
+ {
+ public void update(String username, Credential credential, String[] roleArray);
+
+ public void remove(String username);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleInfo.java b/lib/jetty/org/eclipse/jetty/security/RoleInfo.java
new file mode 100644
index 00000000..55f1ae2e
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/RoleInfo.java
@@ -0,0 +1,162 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * RoleInfo
+ *
+ * Badly named class that holds the role and user data constraint info for a
+ * path/http method combination, extracted and combined from security
+ * constraints.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class RoleInfo
+{
+ private boolean _isAnyAuth;
+ private boolean _isAnyRole;
+ private boolean _checked;
+ private boolean _forbidden;
+ private UserDataConstraint _userDataConstraint;
+
+ /**
+ * List of permitted roles
+ */
+ private final Set _roles = new CopyOnWriteArraySet();
+
+ public RoleInfo()
+ {
+ }
+
+ public boolean isChecked()
+ {
+ return _checked;
+ }
+
+ public void setChecked(boolean checked)
+ {
+ this._checked = checked;
+ if (!checked)
+ {
+ _forbidden=false;
+ _roles.clear();
+ _isAnyRole=false;
+ _isAnyAuth=false;
+ }
+ }
+
+ public boolean isForbidden()
+ {
+ return _forbidden;
+ }
+
+ public void setForbidden(boolean forbidden)
+ {
+ this._forbidden = forbidden;
+ if (forbidden)
+ {
+ _checked = true;
+ _userDataConstraint = null;
+ _isAnyRole=false;
+ _isAnyAuth=false;
+ _roles.clear();
+ }
+ }
+
+ public boolean isAnyRole()
+ {
+ return _isAnyRole;
+ }
+
+ public void setAnyRole(boolean anyRole)
+ {
+ this._isAnyRole=anyRole;
+ if (anyRole)
+ _checked = true;
+ }
+
+ public boolean isAnyAuth ()
+ {
+ return _isAnyAuth;
+ }
+
+ public void setAnyAuth(boolean anyAuth)
+ {
+ this._isAnyAuth=anyAuth;
+ if (anyAuth)
+ _checked = true;
+ }
+
+ public UserDataConstraint getUserDataConstraint()
+ {
+ return _userDataConstraint;
+ }
+
+ public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+ {
+ if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
+ if (this._userDataConstraint == null)
+ {
+
+ this._userDataConstraint = userDataConstraint;
+ }
+ else
+ {
+ this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint);
+ }
+ }
+
+ public Set getRoles()
+ {
+ return _roles;
+ }
+
+ public void addRole(String role)
+ {
+ _roles.add(role);
+ }
+
+ public void combine(RoleInfo other)
+ {
+ if (other._forbidden)
+ setForbidden(true);
+ else if (!other._checked) // TODO is this the right way around???
+ setChecked(true);
+ else if (other._isAnyRole)
+ setAnyRole(true);
+ else if (other._isAnyAuth)
+ setAnyAuth(true);
+ else if (!_isAnyRole)
+ {
+ for (String r : other._roles)
+ _roles.add(r);
+ }
+
+ setUserDataConstraint(other._userDataConstraint);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java
new file mode 100644
index 00000000..13a7ea74
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+
+
+/**
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public class RoleRunAsToken implements RunAsToken
+{
+ private final String _runAsRole;
+
+ public RoleRunAsToken(String runAsRole)
+ {
+ this._runAsRole = runAsRole;
+ }
+
+ public String getRunAsRole()
+ {
+ return _runAsRole;
+ }
+
+ public String toString()
+ {
+ return "RoleRunAsToken("+_runAsRole+")";
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RunAsToken.java
new file mode 100644
index 00000000..639c9726
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/RunAsToken.java
@@ -0,0 +1,27 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * marker interface for run-as-role tokens
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public interface RunAsToken
+{
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java
new file mode 100644
index 00000000..a6e108e9
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java
@@ -0,0 +1,708 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Abstract SecurityHandler.
+ * Select and apply an {@link Authenticator} to a request.
+ *
+ * The Authenticator may either be directly set on the handler
+ * or will be create during {@link #start()} with a call to
+ * either the default or set AuthenticatorFactory.
+ *
+ * SecurityHandler has a set of initparameters that are used by the
+ * Authentication.Configuration. At startup, any context init parameters
+ * that start with "org.eclipse.jetty.security." that do not have
+ * values in the SecurityHandler init parameters, are copied.
+ *
+ */
+public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
+{
+ private static final Logger LOG = Log.getLogger(SecurityHandler.class);
+
+ /* ------------------------------------------------------------ */
+ private boolean _checkWelcomeFiles = false;
+ private Authenticator _authenticator;
+ private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
+ private String _realmName;
+ private String _authMethod;
+ private final Map _initParameters=new HashMap();
+ private LoginService _loginService;
+ private IdentityService _identityService;
+ private boolean _renewSession=true;
+
+ /* ------------------------------------------------------------ */
+ protected SecurityHandler()
+ {
+ addBean(_authenticatorFactory);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the identityService.
+ * @return the identityService
+ */
+ @Override
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the identityService.
+ * @param identityService the identityService to set
+ */
+ public void setIdentityService(IdentityService identityService)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_identityService,identityService);
+ _identityService = identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the loginService.
+ * @return the loginService
+ */
+ @Override
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the loginService.
+ * @param loginService the loginService to set
+ */
+ public void setLoginService(LoginService loginService)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_loginService,loginService);
+ _loginService = loginService;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public Authenticator getAuthenticator()
+ {
+ return _authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the authenticator.
+ * @param authenticator
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthenticator(Authenticator authenticator)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_authenticator,authenticator);
+ _authenticator = authenticator;
+ if (_authenticator!=null)
+ _authMethod=_authenticator.getAuthMethod();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the authenticatorFactory
+ */
+ public Authenticator.Factory getAuthenticatorFactory()
+ {
+ return _authenticatorFactory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authenticatorFactory the authenticatorFactory to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ updateBean(_authenticatorFactory,authenticatorFactory);
+ _authenticatorFactory = authenticatorFactory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the realmName
+ */
+ @Override
+ public String getRealmName()
+ {
+ return _realmName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param realmName the realmName to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setRealmName(String realmName)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _realmName = realmName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the authMethod
+ */
+ @Override
+ public String getAuthMethod()
+ {
+ return _authMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authMethod the authMethod to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthMethod(String authMethod)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _authMethod = authMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if forwards to welcome files are authenticated
+ */
+ public boolean isCheckWelcomeFiles()
+ {
+ return _checkWelcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authenticateWelcomeFiles True if forwards to welcome files are
+ * authenticated
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _checkWelcomeFiles = authenticateWelcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getInitParameter(String key)
+ {
+ return _initParameters.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set getInitParameterNames()
+ {
+ return _initParameters.keySet();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set an initialization parameter.
+ * @param key
+ * @param value
+ * @return previous value
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public String setInitParameter(String key, String value)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ return _initParameters.put(key,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected LoginService findLoginService() throws Exception
+ {
+ Collection list = getServer().getBeans(LoginService.class);
+ LoginService service = null;
+ String realm=getRealmName();
+ if (realm!=null)
+ {
+ for (LoginService s : list)
+ if (s.getName()!=null && s.getName().equals(realm))
+ {
+ service=s;
+ break;
+ }
+ }
+ else if (list.size()==1)
+ service = list.iterator().next();
+
+ return service;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected IdentityService findIdentityService()
+ {
+ return getServer().getBean(IdentityService.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ @Override
+ protected void doStart()
+ throws Exception
+ {
+ // copy security init parameters
+ ContextHandler.Context context =ContextHandler.getCurrentContext();
+ if (context!=null)
+ {
+ Enumeration names=context.getInitParameterNames();
+ while (names!=null && names.hasMoreElements())
+ {
+ String name =names.nextElement();
+ if (name.startsWith("org.eclipse.jetty.security.") &&
+ getInitParameter(name)==null)
+ setInitParameter(name,context.getInitParameter(name));
+ }
+
+ //register a session listener to handle securing sessions when authentication is performed
+ context.getContextHandler().addEventListener(new HttpSessionListener()
+ {
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se)
+ {
+ }
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se)
+ {
+ //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
+ HttpChannel> channel = HttpChannel.getCurrentHttpChannel();
+
+ if (channel == null)
+ return;
+ Request request = channel.getRequest();
+ if (request == null)
+ return;
+
+ if (request.isSecure())
+ {
+ se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ }
+ }
+ });
+ }
+
+ // complicated resolution of login and identity service to handle
+ // many different ways these can be constructed and injected.
+
+ if (_loginService==null)
+ {
+ setLoginService(findLoginService());
+ if (_loginService!=null)
+ unmanage(_loginService);
+ }
+
+ if (_identityService==null)
+ {
+ if (_loginService!=null)
+ setIdentityService(_loginService.getIdentityService());
+
+ if (_identityService==null)
+ setIdentityService(findIdentityService());
+
+ if (_identityService==null)
+ {
+ if (_realmName!=null)
+ {
+ setIdentityService(new DefaultIdentityService());
+ manage(_identityService);
+ }
+ }
+ else
+ unmanage(_identityService);
+ }
+
+ if (_loginService!=null)
+ {
+ if (_loginService.getIdentityService()==null)
+ _loginService.setIdentityService(_identityService);
+ else if (_loginService.getIdentityService()!=_identityService)
+ throw new IllegalStateException("LoginService has different IdentityService to "+this);
+ }
+
+ Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
+ if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
+ setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
+
+ if (_authenticator!=null)
+ _authenticator.setConfiguration(this);
+ else if (_realmName!=null)
+ {
+ LOG.warn("No Authenticator for "+this);
+ throw new IllegalStateException("No Authenticator");
+ }
+
+ super.doStart();
+ }
+
+ @Override
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ //if we discovered the services (rather than had them explicitly configured), remove them.
+ if (!isManaged(_identityService))
+ {
+ removeBean(_identityService);
+ _identityService = null;
+ }
+
+ if (!isManaged(_loginService))
+ {
+ removeBean(_loginService);
+ _loginService=null;
+ }
+
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean checkSecurity(Request request)
+ {
+ switch(request.getDispatcherType())
+ {
+ case REQUEST:
+ case ASYNC:
+ return true;
+ case FORWARD:
+ if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+ {
+ request.removeAttribute("org.eclipse.jetty.server.welcome");
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+ */
+ @Override
+ public boolean isSessionRenewedOnAuthentication()
+ {
+ return _renewSession;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set renew the session on Authentication.
+ *
+ * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
+ * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+ */
+ public void setSessionRenewedOnAuthentication(boolean renew)
+ {
+ _renewSession=renew;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
+ * javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ final Response base_response = baseRequest.getResponse();
+ final Handler handler=getHandler();
+
+ if (handler==null)
+ return;
+
+ final Authenticator authenticator = _authenticator;
+
+ if (checkSecurity(baseRequest))
+ {
+ //See Servlet Spec 3.1 sec 13.6.3
+ if (authenticator != null)
+ authenticator.prepareRequest(baseRequest);
+
+ RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
+
+ // Check data constraints
+ if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
+ {
+ if (!baseRequest.isHandled())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ baseRequest.setHandled(true);
+ }
+ return;
+ }
+
+ // is Auth mandatory?
+ boolean isAuthMandatory =
+ isAuthMandatory(baseRequest, base_response, roleInfo);
+
+ if (isAuthMandatory && authenticator==null)
+ {
+ LOG.warn("No authenticator for: "+roleInfo);
+ if (!baseRequest.isHandled())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ baseRequest.setHandled(true);
+ }
+ return;
+ }
+
+ // check authentication
+ Object previousIdentity = null;
+ try
+ {
+ Authentication authentication = baseRequest.getAuthentication();
+ if (authentication==null || authentication==Authentication.NOT_CHECKED)
+ authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
+
+ if (authentication instanceof Authentication.Wrapped)
+ {
+ request=((Authentication.Wrapped)authentication).getHttpServletRequest();
+ response=((Authentication.Wrapped)authentication).getHttpServletResponse();
+ }
+
+ if (authentication instanceof Authentication.ResponseSent)
+ {
+ baseRequest.setHandled(true);
+ }
+ else if (authentication instanceof Authentication.User)
+ {
+ Authentication.User userAuth = (Authentication.User)authentication;
+ baseRequest.setAuthentication(authentication);
+ if (_identityService!=null)
+ previousIdentity = _identityService.associate(userAuth.getUserIdentity());
+
+ if (isAuthMandatory)
+ {
+ boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
+ if (!authorized)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
+ baseRequest.setHandled(true);
+ return;
+ }
+ }
+
+ handler.handle(pathInContext, baseRequest, request, response);
+ if (authenticator!=null)
+ authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+ }
+ else if (authentication instanceof Authentication.Deferred)
+ {
+ DeferredAuthentication deferred= (DeferredAuthentication)authentication;
+ baseRequest.setAuthentication(authentication);
+
+ try
+ {
+ handler.handle(pathInContext, baseRequest, request, response);
+ }
+ finally
+ {
+ previousIdentity = deferred.getPreviousAssociation();
+ }
+
+ if (authenticator!=null)
+ {
+ Authentication auth=baseRequest.getAuthentication();
+ if (auth instanceof Authentication.User)
+ {
+ Authentication.User userAuth = (Authentication.User)auth;
+ authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+ }
+ else
+ authenticator.secureResponse(request, response, isAuthMandatory, null);
+ }
+ }
+ else
+ {
+ baseRequest.setAuthentication(authentication);
+ if (_identityService!=null)
+ previousIdentity = _identityService.associate(null);
+ handler.handle(pathInContext, baseRequest, request, response);
+ if (authenticator!=null)
+ authenticator.secureResponse(request, response, isAuthMandatory, null);
+ }
+ }
+ catch (ServerAuthException e)
+ {
+ // jaspi 3.8.3 send HTTP 500 internal server error, with message
+ // from AuthException
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+ finally
+ {
+ if (_identityService!=null)
+ _identityService.disassociate(previousIdentity);
+ }
+ }
+ else
+ handler.handle(pathInContext, baseRequest, request, response);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static SecurityHandler getCurrentSecurityHandler()
+ {
+ Context context = ContextHandler.getCurrentContext();
+ if (context==null)
+ return null;
+
+ return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void logout(Authentication.User user)
+ {
+ LOG.debug("logout {}",user);
+ LoginService login_service=getLoginService();
+ if (login_service!=null)
+ {
+ login_service.logout(user.getUserIdentity());
+ }
+
+ IdentityService identity_service=getIdentityService();
+ if (identity_service!=null)
+ {
+ // TODO recover previous from threadlocal (or similar)
+ Object previous=null;
+ identity_service.disassociate(previous);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
+ UserIdentity userIdentity) throws IOException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class NotChecked implements Principal
+ {
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "NOT CHECKED";
+ }
+
+ public SecurityHandler getSecurityHandler()
+ {
+ return SecurityHandler.this;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static final Principal __NO_USER = new Principal()
+ {
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "No User";
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
+ * of authentication. A request with a Nobody UserPrincipal will be allowed
+ * past all authentication constraints - but will not be considered an
+ * authenticated request. It can be used by Authenticators such as
+ * FormAuthenticator to allow access to logon and error pages within an
+ * authenticated URI tree.
+ */
+ public static final Principal __NOBODY = new Principal()
+ {
+ @Override
+ public String getName()
+ {
+ return "Nobody";
+ }
+
+ @Override
+ public String toString()
+ {
+ return getName();
+ }
+ };
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java b/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java
new file mode 100644
index 00000000..85f532fa
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public class ServerAuthException extends GeneralSecurityException
+{
+
+ public ServerAuthException()
+ {
+ }
+
+ public ServerAuthException(String s)
+ {
+ super(s);
+ }
+
+ public ServerAuthException(String s, Throwable throwable)
+ {
+ super(s, throwable);
+ }
+
+ public ServerAuthException(Throwable throwable)
+ {
+ super(throwable);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java b/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java
new file mode 100644
index 00000000..ba160f06
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java
@@ -0,0 +1,191 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Properties;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
+{
+ private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
+
+ protected IdentityService _identityService;// = new LdapIdentityService();
+ protected String _name;
+ private String _config;
+
+ private String _targetName;
+
+ public SpnegoLoginService()
+ {
+
+ }
+
+ public SpnegoLoginService( String name )
+ {
+ setName(name);
+ }
+
+ public SpnegoLoginService( String name, String config )
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ @Override
+ public String getName()
+ {
+ return _name;
+ }
+
+ public void setName(String name)
+ {
+ if (isRunning())
+ {
+ throw new IllegalStateException("Running");
+ }
+
+ _name = name;
+ }
+
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ public void setConfig( String config )
+ {
+ if (isRunning())
+ {
+ throw new IllegalStateException("Running");
+ }
+
+ _config = config;
+ }
+
+
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ Properties properties = new Properties();
+ Resource resource = Resource.newResource(_config);
+ properties.load(resource.getInputStream());
+
+ _targetName = properties.getProperty("targetName");
+
+ LOG.debug("Target Name {}", _targetName);
+
+ super.doStart();
+ }
+
+ /**
+ * username will be null since the credentials will contain all the relevant info
+ */
+ @Override
+ public UserIdentity login(String username, Object credentials)
+ {
+ String encodedAuthToken = (String)credentials;
+
+ byte[] authToken = B64Code.decode(encodedAuthToken);
+
+ GSSManager manager = GSSManager.getInstance();
+ try
+ {
+ Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ GSSName gssName = manager.createName(_targetName,null);
+ GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
+ GSSContext gContext = manager.createContext(serverCreds);
+
+ if (gContext == null)
+ {
+ LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
+ }
+ else
+ {
+ while (!gContext.isEstablished())
+ {
+ authToken = gContext.acceptSecContext(authToken,0,authToken.length);
+ }
+ if (gContext.isEstablished())
+ {
+ String clientName = gContext.getSrcName().toString();
+ String role = clientName.substring(clientName.indexOf('@') + 1);
+
+ LOG.debug("SpnegoUserRealm: established a security context");
+ LOG.debug("Client Principal is: " + gContext.getSrcName());
+ LOG.debug("Server Principal is: " + gContext.getTargName());
+ LOG.debug("Client Default Role: " + role);
+
+ SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
+
+ Subject subject = new Subject();
+ subject.getPrincipals().add(user);
+
+ return _identityService.newUserIdentity(subject,user, new String[]{role});
+ }
+ }
+
+ }
+ catch (GSSException gsse)
+ {
+ LOG.warn(gsse);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user)
+ {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service)
+ {
+ _identityService = service;
+ }
+
+ @Override
+ public void logout(UserIdentity user)
+ {
+ // TODO Auto-generated method stub
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java
new file mode 100644
index 00000000..13cf0bb1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+public class SpnegoUserIdentity implements UserIdentity
+{
+ private Subject _subject;
+ private Principal _principal;
+ private List _roles;
+
+ public SpnegoUserIdentity( Subject subject, Principal principal, List roles )
+ {
+ _subject = subject;
+ _principal = principal;
+ _roles = roles;
+ }
+
+
+ public Subject getSubject()
+ {
+ return _subject;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return _principal;
+ }
+
+ public boolean isUserInRole(String role, Scope scope)
+ {
+ return _roles.contains(role);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java
new file mode 100644
index 00000000..3fe9445f
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import org.eclipse.jetty.util.B64Code;
+
+public class SpnegoUserPrincipal implements Principal
+{
+ private final String _name;
+ private byte[] _token;
+ private String _encodedToken;
+
+ public SpnegoUserPrincipal( String name, String encodedToken )
+ {
+ _name = name;
+ _encodedToken = encodedToken;
+ }
+
+ public SpnegoUserPrincipal( String name, byte[] token )
+ {
+ _name = name;
+ _token = token;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public byte[] getToken()
+ {
+ if ( _token == null )
+ {
+ _token = B64Code.decode(_encodedToken);
+ }
+ return _token;
+ }
+
+ public String getEncodedToken()
+ {
+ if ( _encodedToken == null )
+ {
+ _encodedToken = new String(B64Code.encode(_token,true));
+ }
+ return _encodedToken;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java
new file mode 100644
index 00000000..9174a06c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class UserAuthentication extends AbstractUserAuthentication
+{
+
+ public UserAuthentication(String method, UserIdentity userIdentity)
+ {
+ super(method, userIdentity);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return "{User,"+getAuthMethod()+","+_userIdentity+"}";
+ }
+
+ public void logout()
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security!=null)
+ security.logout(this);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java b/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java
new file mode 100644
index 00000000..c288e1dc
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public enum UserDataConstraint
+{
+ None, Integral, Confidential;
+
+ public static UserDataConstraint get(int dataConstraint)
+ {
+ if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint);
+ if (dataConstraint == -1) return None;
+ return values()[dataConstraint];
+ }
+
+ public UserDataConstraint combine(UserDataConstraint other)
+ {
+ if (this.compareTo(other) < 0) return this;
+ return other;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
new file mode 100644
index 00000000..caa784f0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
@@ -0,0 +1,121 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class BasicAuthenticator extends LoginAuthenticator
+{
+ /* ------------------------------------------------------------ */
+ public BasicAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
+ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__BASIC_AUTH;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
+ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ try
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ if (credentials != null)
+ {
+ int space=credentials.indexOf(' ');
+ if (space>0)
+ {
+ String method=credentials.substring(0,space);
+ if ("basic".equalsIgnoreCase(method))
+ {
+ credentials = credentials.substring(space+1);
+ credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
+ int i = credentials.indexOf(':');
+ if (i>0)
+ {
+ String username = credentials.substring(0,i);
+ String password = credentials.substring(i+1);
+
+ UserIdentity user = login (username, password, request);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ }
+ }
+ }
+
+ if (DeferredAuthentication.isDeferred(response))
+ return Authentication.UNAUTHENTICATED;
+
+ response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"');
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException e)
+ {
+ throw new ServerAuthException(e);
+ }
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
new file mode 100644
index 00000000..9694cf15
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
@@ -0,0 +1,371 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class ClientCertAuthenticator extends LoginAuthenticator
+{
+ /** String name of keystore password property. */
+ private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+ /** Truststore path */
+ private String _trustStorePath;
+ /** Truststore provider name */
+ private String _trustStoreProvider;
+ /** Truststore type */
+ private String _trustStoreType = "JKS";
+ /** Truststore password */
+ private transient Password _trustStorePassword;
+
+ /** Set to true if SSL certificate validation is required */
+ private boolean _validateCerts;
+ /** Path to file that contains Certificate Revocation List */
+ private String _crlPath;
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+ /** Location of OCSP Responder */
+ private String _ocspResponderURL;
+
+ public ClientCertAuthenticator()
+ {
+ super();
+ }
+
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__CERT_AUTH;
+ }
+
+
+
+ /**
+ * @return Authentication for request
+ * @throws ServerAuthException
+ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+ try
+ {
+ // Need certificates.
+ if (certs != null && certs.length > 0)
+ {
+
+ if (_validateCerts)
+ {
+ KeyStore trustStore = getKeyStore(null,
+ _trustStorePath, _trustStoreType, _trustStoreProvider,
+ _trustStorePassword == null ? null :_trustStorePassword.toString());
+ Collection extends CRL> crls = loadCRL(_crlPath);
+ CertificateValidator validator = new CertificateValidator(trustStore, crls);
+ validator.validate(certs);
+ }
+
+ for (X509Certificate cert: certs)
+ {
+ if (cert==null)
+ continue;
+
+ Principal principal = cert.getSubjectDN();
+ if (principal == null) principal = cert.getIssuerDN();
+ final String username = principal == null ? "clientcert" : principal.getName();
+
+ final char[] credential = B64Code.encode(cert.getSignature());
+
+ UserIdentity user = login(username, credential, req);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ }
+
+ if (!DeferredAuthentication.isDeferred(response))
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return Authentication.SEND_FAILURE;
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+ catch (Exception e)
+ {
+ throw new ServerAuthException(e.getMessage());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads keystore using an input stream or a file path in the same
+ * order of precedence.
+ *
+ * Required for integrations to be able to override the mechanism
+ * used to load a keystore in order to provide their own implementation.
+ *
+ * @param storeStream keystore input stream
+ * @param storePath path of keystore file
+ * @param storeType keystore type
+ * @param storeProvider keystore provider
+ * @param storePassword keystore password
+ * @return created keystore
+ * @throws Exception
+ */
+ protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+ {
+ return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads certificate revocation list (CRL) from a file.
+ *
+ * Required for integrations to be able to override the mechanism used to
+ * load CRL in order to provide their own implementation.
+ *
+ * @param crlPath path of certificate revocation list file
+ * @return a (possibly empty) collection view of java.security.cert.CRL objects initialized with the data from the
+ * input stream.
+ * @throws Exception
+ */
+ protected Collection extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ return CertificateUtils.loadCRL(crlPath);
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if SSL certificate has to be validated
+ */
+ public boolean isValidateCerts()
+ {
+ return _validateCerts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param validateCerts
+ * true if SSL certificates have to be validated
+ */
+ public void setValidateCerts(boolean validateCerts)
+ {
+ _validateCerts = validateCerts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The file name or URL of the trust store location
+ */
+ public String getTrustStore()
+ {
+ return _trustStorePath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStorePath
+ * The file name or URL of the trust store location
+ */
+ public void setTrustStore(String trustStorePath)
+ {
+ _trustStorePath = trustStorePath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The provider of the trust store
+ */
+ public String getTrustStoreProvider()
+ {
+ return _trustStoreProvider;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStoreProvider
+ * The provider of the trust store
+ */
+ public void setTrustStoreProvider(String trustStoreProvider)
+ {
+ _trustStoreProvider = trustStoreProvider;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The type of the trust store (default "JKS")
+ */
+ public String getTrustStoreType()
+ {
+ return _trustStoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStoreType
+ * The type of the trust store (default "JKS")
+ */
+ public void setTrustStoreType(String trustStoreType)
+ {
+ _trustStoreType = trustStoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param password
+ * The password for the trust store
+ */
+ public void setTrustStorePassword(String password)
+ {
+ _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the crlPath.
+ * @return the crlPath
+ */
+ public String getCrlPath()
+ {
+ return _crlPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the crlPath.
+ * @param crlPath the crlPath to set
+ */
+ public void setCrlPath(String crlPath)
+ {
+ _crlPath = crlPath;
+ }
+
+ /**
+ * @return Maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public int getMaxCertPathLength()
+ {
+ return _maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCertPathLength
+ * maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public void setMaxCertPathLength(int maxCertPathLength)
+ {
+ _maxCertPathLength = maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if CRL Distribution Points support is enabled
+ */
+ public boolean isEnableCRLDP()
+ {
+ return _enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables CRL Distribution Points Support
+ * @param enableCRLDP true - turn on, false - turns off
+ */
+ public void setEnableCRLDP(boolean enableCRLDP)
+ {
+ _enableCRLDP = enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if On-Line Certificate Status Protocol support is enabled
+ */
+ public boolean isEnableOCSP()
+ {
+ return _enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables On-Line Certificate Status Protocol support
+ * @param enableOCSP true - turn on, false - turn off
+ */
+ public void setEnableOCSP(boolean enableOCSP)
+ {
+ _enableOCSP = enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Location of the OCSP Responder
+ */
+ public String getOcspResponderURL()
+ {
+ return _ocspResponderURL;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the location of the OCSP Responder.
+ * @param ocspResponderURL location of the OCSP Responder
+ */
+ public void setOcspResponderURL(String ocspResponderURL)
+ {
+ _ocspResponderURL = ocspResponderURL;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
new file mode 100644
index 00000000..df0f7d92
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
@@ -0,0 +1,395 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class DeferredAuthentication implements Authentication.Deferred
+{
+ private static final Logger LOG = Log.getLogger(DeferredAuthentication.class);
+ protected final LoginAuthenticator _authenticator;
+ private Object _previousAssociation;
+
+ /* ------------------------------------------------------------ */
+ public DeferredAuthentication(LoginAuthenticator authenticator)
+ {
+ if (authenticator == null)
+ throw new NullPointerException("No Authenticator");
+ this._authenticator = authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
+ */
+ @Override
+ public Authentication authenticate(ServletRequest request)
+ {
+ try
+ {
+ Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
+
+ if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
+ {
+ LoginService login_service= _authenticator.getLoginService();
+ IdentityService identity_service=login_service.getIdentityService();
+
+ if (identity_service!=null)
+ _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+
+ return authentication;
+ }
+ }
+ catch (ServerAuthException e)
+ {
+ LOG.debug(e);
+ }
+
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ @Override
+ public Authentication authenticate(ServletRequest request, ServletResponse response)
+ {
+ try
+ {
+ LoginService login_service= _authenticator.getLoginService();
+ IdentityService identity_service=login_service.getIdentityService();
+
+ Authentication authentication = _authenticator.validateRequest(request,response,true);
+ if (authentication instanceof Authentication.User && identity_service!=null)
+ _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+ return authentication;
+ }
+ catch (ServerAuthException e)
+ {
+ LOG.debug(e);
+ }
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
+ */
+ @Override
+ public Authentication login(String username, Object password, ServletRequest request)
+ {
+ if (username == null)
+ return null;
+
+ UserIdentity identity = _authenticator.login(username, password, request);
+ if (identity != null)
+ {
+ IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
+ UserAuthentication authentication = new UserAuthentication("API",identity);
+ if (identity_service != null)
+ _previousAssociation=identity_service.associate(identity);
+ return authentication;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getPreviousAssociation()
+ {
+ return _previousAssociation;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param response
+ * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)}
+ */
+ public static boolean isDeferred(HttpServletResponse response)
+ {
+ return response==__deferredResponse;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ final static HttpServletResponse __deferredResponse = new HttpServletResponse()
+ {
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ }
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ }
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ return false;
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeRedirectUrl(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeUrl(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException
+ {
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ }
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ }
+
+ @Override
+ public void setStatus(int sc)
+ {
+ }
+
+ @Override
+ public void setStatus(int sc, String sm)
+ {
+ }
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ return 1024;
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ return null;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return null;
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return null;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ return __nullOut;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ return IO.getNullPrintWriter();
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return true;
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void resetBuffer()
+ {
+ }
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ }
+
+ @Override
+ public void setContentLength(int len)
+ {
+ }
+
+ public void setContentLengthLong(long len)
+ {
+
+ }
+
+ @Override
+ public void setContentType(String type)
+ {
+ }
+
+ @Override
+ public void setLocale(Locale loc)
+ {
+ }
+
+ @Override
+ public Collection getHeaderNames()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getHeader(String arg0)
+ {
+ return null;
+ }
+
+ @Override
+ public Collection getHeaders(String arg0)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return 0;
+ }
+
+
+ };
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static ServletOutputStream __nullOut = new ServletOutputStream()
+ {
+ @Override
+ public void write(int b) throws IOException
+ {
+ }
+
+ @Override
+ public void print(String s) throws IOException
+ {
+ }
+
+ @Override
+ public void println(String s) throws IOException
+ {
+ }
+
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ return false;
+ }
+ };
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
new file mode 100644
index 00000000..13537e6c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
@@ -0,0 +1,421 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ *
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceAge". The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount". When the age or count is exceeded, the nonce is considered stale.
+ */
+public class DigestAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
+ SecureRandom _random = new SecureRandom();
+ private long _maxNonceAgeMs = 60*1000;
+ private int _maxNC=1024;
+ private ConcurrentMap _nonceMap = new ConcurrentHashMap();
+ private Queue _nonceQueue = new ConcurrentLinkedQueue();
+ private static class Nonce
+ {
+ final String _nonce;
+ final long _ts;
+ final BitSet _seen;
+
+ public Nonce(String nonce, long ts, int size)
+ {
+ _nonce=nonce;
+ _ts=ts;
+ _seen = new BitSet(size);
+ }
+
+ public boolean seen(int count)
+ {
+ synchronized (this)
+ {
+ if (count>=_seen.size())
+ return true;
+ boolean s=_seen.get(count);
+ _seen.set(count);
+ return s;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public DigestAuthenticator()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ super.setConfiguration(configuration);
+
+ String mna=configuration.getInitParameter("maxNonceAge");
+ if (mna!=null)
+ {
+ _maxNonceAgeMs=Long.valueOf(mna);
+ }
+ String mnc=configuration.getInitParameter("maxNonceCount");
+ if (mnc!=null)
+ {
+ _maxNC=Integer.valueOf(mnc);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxNonceCount()
+ {
+ return _maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxNonceCount(int maxNC)
+ {
+ _maxNC = maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getMaxNonceAge()
+ {
+ return _maxNonceAgeMs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
+ {
+ _maxNonceAgeMs = maxNonceAgeInMillis;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__DIGEST_AUTH;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ try
+ {
+ boolean stale = false;
+ if (credentials != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Credentials: " + credentials);
+ QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
+ final Digest digest = new Digest(request.getMethod());
+ String last = null;
+ String name = null;
+
+ while (tokenizer.hasMoreTokens())
+ {
+ String tok = tokenizer.nextToken();
+ char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
+
+ switch (c)
+ {
+ case '=':
+ name = last;
+ last = tok;
+ break;
+ case ',':
+ name = null;
+ break;
+ case ' ':
+ break;
+
+ default:
+ last = tok;
+ if (name != null)
+ {
+ if ("username".equalsIgnoreCase(name))
+ digest.username = tok;
+ else if ("realm".equalsIgnoreCase(name))
+ digest.realm = tok;
+ else if ("nonce".equalsIgnoreCase(name))
+ digest.nonce = tok;
+ else if ("nc".equalsIgnoreCase(name))
+ digest.nc = tok;
+ else if ("cnonce".equalsIgnoreCase(name))
+ digest.cnonce = tok;
+ else if ("qop".equalsIgnoreCase(name))
+ digest.qop = tok;
+ else if ("uri".equalsIgnoreCase(name))
+ digest.uri = tok;
+ else if ("response".equalsIgnoreCase(name))
+ digest.response = tok;
+ name=null;
+ }
+ }
+ }
+
+ int n = checkNonce(digest,(Request)request);
+
+ if (n > 0)
+ {
+ //UserIdentity user = _loginService.login(digest.username,digest);
+ UserIdentity user = login(digest.username, digest, req);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ else if (n == 0)
+ stale = true;
+
+ }
+
+ if (!DeferredAuthentication.isDeferred(response))
+ {
+ String domain = request.getContextPath();
+ if (domain == null)
+ domain = "/";
+ response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName()
+ + "\", domain=\""
+ + domain
+ + "\", nonce=\""
+ + newNonce((Request)request)
+ + "\", algorithm=MD5, qop=\"auth\","
+ + " stale=" + stale);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+ return Authentication.SEND_CONTINUE;
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+ catch (IOException e)
+ {
+ throw new ServerAuthException(e);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public String newNonce(Request request)
+ {
+ Nonce nonce;
+
+ do
+ {
+ byte[] nounce = new byte[24];
+ _random.nextBytes(nounce);
+
+ nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
+ }
+ while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
+ _nonceQueue.add(nonce);
+
+ return nonce._nonce;
+ }
+
+ /**
+ * @param nstring nonce to check
+ * @param request
+ * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
+ */
+ /* ------------------------------------------------------------ */
+ private int checkNonce(Digest digest, Request request)
+ {
+ // firstly let's expire old nonces
+ long expired = request.getTimeStamp()-_maxNonceAgeMs;
+ Nonce nonce=_nonceQueue.peek();
+ while (nonce!=null && nonce._ts=_maxNC)
+ return 0;
+
+ if (nonce.seen((int)count))
+ return -1;
+
+ return 1;
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class Digest extends Credential
+ {
+ private static final long serialVersionUID = -2484639019549527724L;
+ final String method;
+ String username = "";
+ String realm = "";
+ String nonce = "";
+ String nc = "";
+ String cnonce = "";
+ String qop = "";
+ String uri = "";
+ String response = "";
+
+ /* ------------------------------------------------------------ */
+ Digest(String m)
+ {
+ method = m;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean check(Object credentials)
+ {
+ if (credentials instanceof char[])
+ credentials=new String((char[])credentials);
+ String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
+
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] ha1;
+ if (credentials instanceof Credential.MD5)
+ {
+ // Credentials are already a MD5 digest - assume it's in
+ // form user:realm:password (we have no way to know since
+ // it's a digest, alright?)
+ ha1 = ((Credential.MD5) credentials).getDigest();
+ }
+ else
+ {
+ // calc A1 digest
+ md.update(username.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+ ha1 = md.digest();
+ }
+ // calc A2 digest
+ md.reset();
+ md.update(method.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
+ byte[] ha2 = md.digest();
+
+ // calc digest
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+ // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+ // <">
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+ // ) > <">
+
+ md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
+ byte[] digest = md.digest();
+
+ // check digest
+ return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return username + "," + response;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
new file mode 100644
index 00000000..dcfea416
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -0,0 +1,564 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ *
+ * This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.
+ *
+ * The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context. FormAuthentication uses
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are associated with the session.
+ *
+ *
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+ public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+ public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+ public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+ public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+ public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+ public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
+ public final static String __J_SECURITY_CHECK = "/j_security_check";
+ public final static String __J_USERNAME = "j_username";
+ public final static String __J_PASSWORD = "j_password";
+
+ private String _formErrorPage;
+ private String _formErrorPath;
+ private String _formLoginPage;
+ private String _formLoginPath;
+ private boolean _dispatch;
+ private boolean _alwaysSaveUri;
+
+ public FormAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public FormAuthenticator(String login,String error,boolean dispatch)
+ {
+ this();
+ if (login!=null)
+ setLoginPage(login);
+ if (error!=null)
+ setErrorPage(error);
+ _dispatch=dispatch;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * If true, uris that cause a redirect to a login page will always
+ * be remembered. If false, only the first uri that leads to a login
+ * page redirect is remembered.
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+ * @param alwaysSave
+ */
+ public void setAlwaysSaveUri (boolean alwaysSave)
+ {
+ _alwaysSaveUri = alwaysSave;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean getAlwaysSaveUri ()
+ {
+ return _alwaysSaveUri;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ super.setConfiguration(configuration);
+ String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+ if (login!=null)
+ setLoginPage(login);
+ String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+ if (error!=null)
+ setErrorPage(error);
+ String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+ _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__FORM_AUTH;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setLoginPage(String path)
+ {
+ if (!path.startsWith("/"))
+ {
+ LOG.warn("form-login-page must start with /");
+ path = "/" + path;
+ }
+ _formLoginPage = path;
+ _formLoginPath = path;
+ if (_formLoginPath.indexOf('?') > 0)
+ _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setErrorPage(String path)
+ {
+ if (path == null || path.trim().length() == 0)
+ {
+ _formErrorPath = null;
+ _formErrorPage = null;
+ }
+ else
+ {
+ if (!path.startsWith("/"))
+ {
+ LOG.warn("form-error-page must start with /");
+ path = "/" + path;
+ }
+ _formErrorPage = path;
+ _formErrorPath = path;
+
+ if (_formErrorPath.indexOf('?') > 0)
+ _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public UserIdentity login(String username, Object password, ServletRequest request)
+ {
+
+ UserIdentity user = super.login(username,password,request);
+ if (user!=null)
+ {
+ HttpSession session = ((HttpServletRequest)request).getSession(true);
+ Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+ session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+ }
+ return user;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //if this is a request resulting from a redirect after auth is complete
+ //(ie its from a redirect to the original request uri) then due to
+ //browser handling of 302 redirects, the method may not be the same as
+ //that of the original request. Replace the method and original post
+ //params (if it was a post).
+ //
+ //See Servlet Spec 3.1 sec 13.6.3
+ HttpServletRequest httpRequest = (HttpServletRequest)request;
+ HttpSession session = httpRequest.getSession(false);
+ if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+ return; //not authenticated yet
+
+ String juri = (String)session.getAttribute(__J_URI);
+ if (juri == null || juri.length() == 0)
+ return; //no original uri saved
+
+ String method = (String)session.getAttribute(__J_METHOD);
+ if (method == null || method.length() == 0)
+ return; //didn't save original request method
+
+ StringBuffer buf = httpRequest.getRequestURL();
+ if (httpRequest.getQueryString() != null)
+ buf.append("?").append(httpRequest.getQueryString());
+
+ if (!juri.equals(buf.toString()))
+ return; //this request is not for the same url as the original
+
+ //restore the original request's method on this request
+ if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ HttpMethod m = HttpMethod.fromString(method);
+ base_request.setMethod(m,m.asString());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String uri = request.getRequestURI();
+ if (uri==null)
+ uri=URIUtil.SLASH;
+
+ mandatory|=isJSecurityCheck(uri);
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+ return new DeferredAuthentication(this);
+
+ HttpSession session = request.getSession(true);
+
+ try
+ {
+ // Handle a request for authentication.
+ if (isJSecurityCheck(uri))
+ {
+ final String username = request.getParameter(__J_USERNAME);
+ final String password = request.getParameter(__J_PASSWORD);
+
+ UserIdentity user = login(username, password, request);
+ LOG.debug("jsecuritycheck {} {}",username,user);
+ session = request.getSession(true);
+ if (user!=null)
+ {
+ // Redirect to original request
+ String nuri;
+ FormAuthentication form_auth;
+ synchronized(session)
+ {
+ nuri = (String) session.getAttribute(__J_URI);
+
+ if (nuri == null || nuri.length() == 0)
+ {
+ nuri = request.getContextPath();
+ if (nuri.length() == 0)
+ nuri = URIUtil.SLASH;
+ }
+ form_auth = new FormAuthentication(getAuthMethod(),user);
+ }
+ LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+ response.setContentLength(0);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+ return form_auth;
+ }
+
+ // not authenticated
+ if (LOG.isDebugEnabled())
+ LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+ if (_formErrorPage == null)
+ {
+ LOG.debug("auth failed {}->403",username);
+ if (response != null)
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ else if (_dispatch)
+ {
+ LOG.debug("auth failed {}=={}",username,_formErrorPage);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+ response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+ dispatcher.forward(new FormRequest(request), new FormResponse(response));
+ }
+ else
+ {
+ LOG.debug("auth failed {}->{}",username,_formErrorPage);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+ }
+
+ return Authentication.SEND_FAILURE;
+ }
+
+ // Look for cached authentication
+ Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+ if (authentication != null)
+ {
+ // Has authentication been revoked?
+ if (authentication instanceof Authentication.User &&
+ _loginService!=null &&
+ !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+ {
+ LOG.debug("auth revoked {}",authentication);
+ session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+ }
+ else
+ {
+ synchronized (session)
+ {
+ String j_uri=(String)session.getAttribute(__J_URI);
+ if (j_uri!=null)
+ {
+ //check if the request is for the same url as the original and restore
+ //params if it was a post
+ LOG.debug("auth retry {}->{}",authentication,j_uri);
+ StringBuffer buf = request.getRequestURL();
+ if (request.getQueryString() != null)
+ buf.append("?").append(request.getQueryString());
+
+ if (j_uri.equals(buf.toString()))
+ {
+ MultiMap j_post = (MultiMap)session.getAttribute(__J_POST);
+ if (j_post!=null)
+ {
+ LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ base_request.setContentParameters(j_post);
+ }
+ session.removeAttribute(__J_URI);
+ session.removeAttribute(__J_METHOD);
+ session.removeAttribute(__J_POST);
+ }
+ }
+ }
+ LOG.debug("auth {}",authentication);
+ return authentication;
+ }
+ }
+
+ // if we can't send challenge
+ if (DeferredAuthentication.isDeferred(response))
+ {
+ LOG.debug("auth deferred {}",session.getId());
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ // remember the current URI
+ synchronized (session)
+ {
+ // But only if it is not set already, or we save every uri that leads to a login form redirect
+ if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+ {
+ StringBuffer buf = request.getRequestURL();
+ if (request.getQueryString() != null)
+ buf.append("?").append(request.getQueryString());
+ session.setAttribute(__J_URI, buf.toString());
+ session.setAttribute(__J_METHOD, request.getMethod());
+
+ if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
+ {
+ Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+ MultiMap formParameters = new MultiMap<>();
+ base_request.extractFormParameters(formParameters);
+ session.setAttribute(__J_POST, formParameters);
+ }
+ }
+ }
+
+ // send the the challenge
+ if (_dispatch)
+ {
+ LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+ response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+ dispatcher.forward(new FormRequest(request), new FormResponse(response));
+ }
+ else
+ {
+ LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+ }
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException | ServletException e)
+ {
+ throw new ServerAuthException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isJSecurityCheck(String uri)
+ {
+ int jsc = uri.indexOf(__J_SECURITY_CHECK);
+
+ if (jsc<0)
+ return false;
+ int e=jsc+__J_SECURITY_CHECK.length();
+ if (e==uri.length())
+ return true;
+ char c = uri.charAt(e);
+ return c==';'||c=='#'||c=='/'||c=='?';
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isLoginOrErrorPage(String pathInContext)
+ {
+ return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected static class FormRequest extends HttpServletRequestWrapper
+ {
+ public FormRequest(HttpServletRequest request)
+ {
+ super(request);
+ }
+
+ @Override
+ public long getDateHeader(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return -1;
+ return super.getDateHeader(name);
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return null;
+ return super.getHeader(name);
+ }
+
+ @Override
+ public Enumeration getHeaderNames()
+ {
+ return Collections.enumeration(Collections.list(super.getHeaderNames()));
+ }
+
+ @Override
+ public Enumeration getHeaders(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return Collections.enumeration(Collections.emptyList());
+ return super.getHeaders(name);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected static class FormResponse extends HttpServletResponseWrapper
+ {
+ public FormResponse(HttpServletResponse response)
+ {
+ super(response);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ if (notIgnored(name))
+ super.addDateHeader(name,date);
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ if (notIgnored(name))
+ super.addHeader(name,value);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ if (notIgnored(name))
+ super.setDateHeader(name,date);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ if (notIgnored(name))
+ super.setHeader(name,value);
+ }
+
+ private boolean notIgnored(String name)
+ {
+ if (HttpHeader.CACHE_CONTROL.is(name) ||
+ HttpHeader.PRAGMA.is(name) ||
+ HttpHeader.ETAG.is(name) ||
+ HttpHeader.EXPIRES.is(name) ||
+ HttpHeader.LAST_MODIFIED.is(name) ||
+ HttpHeader.AGE.is(name))
+ return false;
+ return true;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** This Authentication represents a just completed Form authentication.
+ * Subsequent requests from the same user are authenticated by the presents
+ * of a {@link SessionAuthentication} instance in their session.
+ */
+ public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+ {
+ public FormAuthentication(String method, UserIdentity userIdentity)
+ {
+ super(method,userIdentity);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Form"+super.toString();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
new file mode 100644
index 00000000..9a1940d8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
@@ -0,0 +1,133 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class LoginAuthenticator implements Authenticator
+{
+ private static final Logger LOG = Log.getLogger(LoginAuthenticator.class);
+
+ protected LoginService _loginService;
+ protected IdentityService _identityService;
+ private boolean _renewSession;
+
+
+ /* ------------------------------------------------------------ */
+ protected LoginAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //empty implementation as the default
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity login(String username, Object password, ServletRequest request)
+ {
+ UserIdentity user = _loginService.login(username,password);
+ if (user!=null)
+ {
+ renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
+ return user;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ _loginService=configuration.getLoginService();
+ if (_loginService==null)
+ throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
+ _identityService=configuration.getIdentityService();
+ if (_identityService==null)
+ throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
+ _renewSession=configuration.isSessionRenewedOnAuthentication();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Change the session id.
+ * The session is changed to a new instance with a new ID if and only if:
+ * A session exists.
+ * The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
+ * The session ID has been given to unauthenticated responses
+ *
+ * @param request
+ * @param response
+ * @return The new session.
+ */
+ protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
+ {
+ HttpSession httpSession = request.getSession(false);
+
+ if (_renewSession && httpSession!=null)
+ {
+ synchronized (httpSession)
+ {
+ //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+ //(indicated by SESSION_SECURED not being set on the session) then we should change id
+ if (httpSession.getAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+ {
+ if (httpSession instanceof AbstractSession)
+ {
+ AbstractSession abstractSession = (AbstractSession)httpSession;
+ String oldId = abstractSession.getId();
+ abstractSession.renewId(request);
+ abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ if (abstractSession.isIdChanged() && response != null && (response instanceof Response))
+ ((Response)response).addCookie(abstractSession.getSessionManager().getSessionCookie(abstractSession, request.getContextPath(), request.isSecure()));
+ LOG.debug("renew {}->{}",oldId,abstractSession.getId());
+ }
+ else
+ LOG.warn("Unable to renew session "+httpSession);
+
+ return httpSession;
+ }
+ }
+ }
+ return httpSession;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java
new file mode 100644
index 00000000..e123b221
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
+ */
+public interface LoginCallback
+{
+ public Subject getSubject();
+
+ public String getUserName();
+
+ public Object getCredential();
+
+ public boolean isSuccess();
+
+ public void setSuccess(boolean success);
+
+ public Principal getUserPrincipal();
+
+ public void setUserPrincipal(Principal userPrincipal);
+
+ public String[] getRoles();
+
+ public void setRoles(String[] roles);
+
+ public void clearPassword();
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
new file mode 100644
index 00000000..5939caf7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
@@ -0,0 +1,109 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.IdentityService;
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class LoginCallbackImpl implements LoginCallback
+{
+ // initial data
+ private final Subject subject;
+
+ private final String userName;
+
+ private Object credential;
+
+ private boolean success;
+
+ private Principal userPrincipal;
+
+ private String[] roles = IdentityService.NO_ROLES;
+
+ //TODO could use Credential instance instead of Object if Basic/Form create a Password object
+ public LoginCallbackImpl (Subject subject, String userName, Object credential)
+ {
+ this.subject = subject;
+ this.userName = userName;
+ this.credential = credential;
+ }
+
+ public Subject getSubject()
+ {
+ return subject;
+ }
+
+ public String getUserName()
+ {
+ return userName;
+ }
+
+ public Object getCredential()
+ {
+ return credential;
+ }
+
+ public boolean isSuccess()
+ {
+ return success;
+ }
+
+ public void setSuccess(boolean success)
+ {
+ this.success = success;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return userPrincipal;
+ }
+
+ public void setUserPrincipal(Principal userPrincipal)
+ {
+ this.userPrincipal = userPrincipal;
+ }
+
+ public String[] getRoles()
+ {
+ return roles;
+ }
+
+ public void setRoles(String[] groups)
+ {
+ this.roles = groups;
+ }
+
+ public void clearPassword()
+ {
+ if (credential != null)
+ {
+ credential = null;
+ }
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java
new file mode 100644
index 00000000..e3be5846
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java
@@ -0,0 +1,131 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.security.AbstractUserAuthentication;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+{
+ private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
+
+ private static final long serialVersionUID = -4643200685888258706L;
+
+
+
+ public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
+
+ private final String _name;
+ private final Object _credentials;
+ private transient HttpSession _session;
+
+ public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
+ {
+ super(method, userIdentity);
+ _name=userIdentity.getUserPrincipal().getName();
+ _credentials=credentials;
+ }
+
+
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException
+ {
+ stream.defaultReadObject();
+
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security==null)
+ throw new IllegalStateException("!SecurityHandler");
+ LoginService login_service=security.getLoginService();
+ if (login_service==null)
+ throw new IllegalStateException("!LoginService");
+
+ _userIdentity=login_service.login(_name,_credentials);
+ LOG.debug("Deserialized and relogged in {}",this);
+ }
+
+ public void logout()
+ {
+ if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
+ _session.removeAttribute(__J_AUTHENTICATED);
+
+ doLogout();
+ }
+
+ private void doLogout()
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security!=null)
+ security.logout(this);
+ if (_session!=null)
+ _session.removeAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s,%s}",this.getClass().getSimpleName(),hashCode(),_session==null?"-":_session.getId(),_userIdentity);
+ }
+
+ @Override
+ public void sessionWillPassivate(HttpSessionEvent se)
+ {
+
+ }
+
+ @Override
+ public void sessionDidActivate(HttpSessionEvent se)
+ {
+ if (_session==null)
+ {
+ _session=se.getSession();
+ }
+ }
+
+ @Override
+ public void valueBound(HttpSessionBindingEvent event)
+ {
+ if (_session==null)
+ {
+ _session=event.getSession();
+ }
+ }
+
+ @Override
+ public void valueUnbound(HttpSessionBindingEvent event)
+ {
+ doLogout();
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
new file mode 100644
index 00000000..8469c0a8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
@@ -0,0 +1,116 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SpnegoAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
+ private String _authMethod = Constraint.__SPNEGO_AUTH;
+
+ public SpnegoAuthenticator()
+ {
+ }
+
+ /**
+ * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
+ * @param authMethod
+ */
+ public SpnegoAuthenticator( String authMethod )
+ {
+ _authMethod = authMethod;
+ }
+
+ @Override
+ public String getAuthMethod()
+ {
+ return _authMethod;
+ }
+
+ @Override
+ public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest req = (HttpServletRequest)request;
+ HttpServletResponse res = (HttpServletResponse)response;
+
+ String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ if (!mandatory)
+ {
+ return new DeferredAuthentication(this);
+ }
+
+ // check to see if we have authorization headers required to continue
+ if ( header == null )
+ {
+ try
+ {
+ if (DeferredAuthentication.isDeferred(res))
+ {
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ LOG.debug("SpengoAuthenticator: sending challenge");
+ res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
+ res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException ioe)
+ {
+ throw new ServerAuthException(ioe);
+ }
+ }
+ else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString()))
+ {
+ String spnegoToken = header.substring(10);
+
+ UserIdentity user = login(null,spnegoToken, request);
+
+ if ( user != null )
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java b/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java
new file mode 100644
index 00000000..0e0c617b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty Security : Authenticators and Callbacks
+ */
+package org.eclipse.jetty.security.authentication;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/package-info.java b/lib/jetty/org/eclipse/jetty/security/package-info.java
new file mode 100644
index 00000000..edb5ea4e
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/security/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty Security : Modular Support for Security in Jetty
+ */
+package org.eclipse.jetty.security;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java
new file mode 100644
index 00000000..96fb3a2d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public abstract class AbstractConnectionFactory extends ContainerLifeCycle implements ConnectionFactory
+{
+ private final String _protocol;
+ private int _inputbufferSize = 8192;
+
+ protected AbstractConnectionFactory(String protocol)
+ {
+ _protocol=protocol;
+ }
+
+ @Override
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ public int getInputBufferSize()
+ {
+ return _inputbufferSize;
+ }
+
+ public void setInputBufferSize(int size)
+ {
+ _inputbufferSize=size;
+ }
+
+ protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+ {
+ connection.setInputBufferSize(getInputBufferSize());
+
+ if (connector instanceof ContainerLifeCycle)
+ {
+ ContainerLifeCycle aggregate = (ContainerLifeCycle)connector;
+ for (Connection.Listener listener : aggregate.getBeans(Connection.Listener.class))
+ connection.addListener(listener);
+ }
+ return connection;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+ }
+
+ public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories)
+ {
+ factories=ArrayUtil.removeNulls(factories);
+
+ if (sslContextFactory==null)
+ return factories;
+
+ for (ConnectionFactory factory : factories)
+ {
+ if (factory instanceof HttpConfiguration.ConnectionFactory)
+ {
+ HttpConfiguration config = ((HttpConfiguration.ConnectionFactory)factory).getHttpConfiguration();
+ if (config.getCustomizer(SecureRequestCustomizer.class)==null)
+ config.addCustomizer(new SecureRequestCustomizer());
+ }
+ }
+ return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class);
+
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java
new file mode 100644
index 00000000..7d3402fb
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java
@@ -0,0 +1,564 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
+ * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).
+ *
+ * Connector Services
+ * The abstract connector manages the dependent services needed by all specific connector instances:
+ *
+ * The {@link Executor} service is used to run all active tasks needed by this connector such as accepting connections
+ * or handle HTTP requests. The default is to use the {@link Server#getThreadPool()} as an executor.
+ *
+ * The {@link Scheduler} service is used to monitor the idle timeouts of all connections and is also made available
+ * to the connections to time such things as asynchronous request timeouts. The default is to use a new
+ * {@link ScheduledExecutorScheduler} instance.
+ *
+ * The {@link ByteBufferPool} service is made available to all connections to be used to acquire and release
+ * {@link ByteBuffer} instances from a pool. The default is to use a new {@link ArrayByteBufferPool} instance.
+ *
+ *
+ * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and
+ * may either be managed or unmanaged beans.
+ *
+ * Connection Factories
+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their
+ * protocol name. The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name
+ * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for
+ * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's
+ * next protocol.
+ *
+ * Configuring Connection Factories
+ * The collection of available {@link ConnectionFactory} may be constructor injected or modified with the
+ * methods {@link #addConnectionFactory(ConnectionFactory)}, {@link #removeConnectionFactory(String)} and
+ * {@link #setConnectionFactories(Collection)}. Only a single {@link ConnectionFactory} instance may be configured
+ * per protocol name, so if two factories with the same {@link ConnectionFactory#getProtocol()} are set, then
+ * the second will replace the first.
+ *
+ * The protocol factory used for newly accepted connections is specified by
+ * the method {@link #setDefaultProtocol(String)} or defaults to the protocol of the first configured factory.
+ *
+ * Each Connection factory type is responsible for the configuration of the protocols that it accepts. Thus to
+ * configure the HTTP protocol, you pass a {@link HttpConfiguration} instance to the {@link HttpConnectionFactory}
+ * (or the SPDY factories that can also provide HTTP Semantics). Similarly the {@link SslConnectionFactory} is
+ * configured by passing it a {@link SslContextFactory} and a next protocol name.
+ *
+ *
Connection Factory Operation
+ * {@link ConnectionFactory}s may simply create a {@link Connection} instance to support a specific
+ * protocol. For example, the {@link HttpConnectionFactory} will create a {@link HttpConnection} instance
+ * that can handle http/1.1, http/1.0 and http/0.9.
+ *
+ * {@link ConnectionFactory}s may also create a chain of {@link Connection} instances, using other {@link ConnectionFactory} instances.
+ * For example, the {@link SslConnectionFactory} is configured with a next protocol name, so that once it has accepted
+ * a connection and created an {@link SslConnection}, it then used the next {@link ConnectionFactory} from the
+ * connector using the {@link #getConnectionFactory(String)} method, to create a {@link Connection} instance that
+ * will handle the unecrypted bytes from the {@link SslConnection}. If the next protocol is "http/1.1", then the
+ * {@link SslConnectionFactory} will have a protocol name of "SSL-http/1.1" and lookup "http/1.1" for the protocol
+ * to run over the SSL connection.
+ *
+ * {@link ConnectionFactory}s may also create temporary {@link Connection} instances that will exchange bytes
+ * over the connection to determine what is the next protocol to use. For example the NPN protocol is an extension
+ * of SSL to allow a protocol to be specified during the SSL handshake. NPN is used by the SPDY protocol to
+ * negotiate the version of SPDY or HTTP that the client and server will speak. Thus to accept a SPDY connection, the
+ * connector will be configured with {@link ConnectionFactory}s for "SSL-NPN", "NPN", "spdy/3", "spdy/2", "http/1.1"
+ * with the default protocol being "SSL-NPN". Thus a newly accepted connection uses "SSL-NPN", which specifies a
+ * SSLConnectionFactory with "NPN" as the next protocol. Thus an SslConnection instance is created chained to an NPNConnection
+ * instance. The NPN connection then negotiates with the client to determined the next protocol, which could be
+ * "spdy/3", "spdy/2" or the default of "http/1.1". Once the next protocol is determined, the NPN connection
+ * calls {@link #getConnectionFactory(String)} to create a connection instance that will replace the NPN connection as
+ * the connection chained to the SSLConnection.
+ *
+ *
Acceptors
+ * The connector will execute a number of acceptor tasks to the {@link Exception} service passed to the constructor.
+ * The acceptor tasks run in a loop while the connector is running and repeatedly call the abstract {@link #accept(int)} method.
+ * The implementation of the accept method must:
+ *
+ * block waiting for new connections
+ * accept the connection (eg socket accept)
+ * perform any configuration of the connection (eg. socket linger times)
+ * call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
+ * method to create a new Connection instance.
+ *
+ * The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
+ * the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive). Typically the default is
+ * sufficient for modern persistent protocols (HTTP/1.1, SPDY etc.)
+ */
+@ManagedObject("Abstract implementation of the Connector Interface")
+public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
+{
+ protected final Logger LOG = Log.getLogger(getClass());
+ // Order is important on server side, so we use a LinkedHashMap
+ private final Map _factories = new LinkedHashMap<>();
+ private final Server _server;
+ private final Executor _executor;
+ private final Scheduler _scheduler;
+ private final ByteBufferPool _byteBufferPool;
+ private final Thread[] _acceptors;
+ private final Set _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
+ private final Set _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
+ private volatile CountDownLatch _stopping;
+ private long _idleTimeout = 30000;
+ private String _defaultProtocol;
+ private ConnectionFactory _defaultConnectionFactory;
+ private String _name;
+
+
+ /**
+ * @param server The server this connector will be added to. Must not be null.
+ * @param executor An executor for this connector or null to use the servers executor
+ * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance.
+ * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new {@link ArrayByteBufferPool} instance.
+ * @param acceptors the number of acceptor threads to use, or 0 for a default value.
+ * @param factories The Connection Factories to use.
+ */
+ public AbstractConnector(
+ Server server,
+ Executor executor,
+ Scheduler scheduler,
+ ByteBufferPool pool,
+ int acceptors,
+ ConnectionFactory... factories)
+ {
+ _server=server;
+ _executor=executor!=null?executor:_server.getThreadPool();
+ if (scheduler==null)
+ scheduler=_server.getBean(Scheduler.class);
+ _scheduler=scheduler!=null?scheduler:new ScheduledExecutorScheduler();
+ if (pool==null)
+ pool=_server.getBean(ByteBufferPool.class);
+ _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
+
+ addBean(_server,false);
+ addBean(_executor);
+ if (executor==null)
+ unmanage(_executor); // inherited from server
+ addBean(_scheduler);
+ addBean(_byteBufferPool);
+
+ for (ConnectionFactory factory:factories)
+ addConnectionFactory(factory);
+
+ int cores = Runtime.getRuntime().availableProcessors();
+ if (acceptors < 0)
+ acceptors = 1 + cores / 16;
+ if (acceptors > 2 * cores)
+ LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
+ _acceptors = new Thread[acceptors];
+ }
+
+
+ @Override
+ public Server getServer()
+ {
+ return _server;
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ @Override
+ public ByteBufferPool getByteBufferPool()
+ {
+ return _byteBufferPool;
+ }
+
+ @Override
+ @ManagedAttribute("Idle timeout")
+ public long getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ /**
+ * Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
+ * call, although with NIO implementations other mechanisms may be used to implement the timeout.
+ * The max idle time is applied:
+ *
+ * When waiting for a new message to be received on a connection
+ * When waiting for a new message to be sent on a connection
+ *
+ * This value is interpreted as the maximum time between some progress being made on the connection.
+ * So if a single byte is read or written, then the timeout is reset.
+ *
+ * @param idleTimeout the idle timeout
+ */
+ public void setIdleTimeout(long idleTimeout)
+ {
+ _idleTimeout = idleTimeout;
+ }
+
+ /**
+ * @return Returns the number of acceptor threads.
+ */
+ @ManagedAttribute("number of acceptor threads")
+ public int getAcceptors()
+ {
+ return _acceptors.length;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
+ if(_defaultConnectionFactory==null)
+ throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+
+ super.doStart();
+
+ _stopping=new CountDownLatch(_acceptors.length);
+ for (int i = 0; i < _acceptors.length; i++)
+ getExecutor().execute(new Acceptor(i));
+
+ LOG.info("Started {}", this);
+ }
+
+
+ protected void interruptAcceptors()
+ {
+ synchronized (this)
+ {
+ for (Thread thread : _acceptors)
+ {
+ if (thread != null)
+ thread.interrupt();
+ }
+ }
+ }
+
+ @Override
+ public Future shutdown()
+ {
+ return new FutureCallback(true);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ // Tell the acceptors we are stopping
+ interruptAcceptors();
+
+ // If we have a stop timeout
+ long stopTimeout = getStopTimeout();
+ CountDownLatch stopping=_stopping;
+ if (stopTimeout > 0 && stopping!=null)
+ stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+ _stopping=null;
+
+ super.doStop();
+
+ LOG.info("Stopped {}", this);
+ }
+
+ public void join() throws InterruptedException
+ {
+ join(0);
+ }
+
+ public void join(long timeout) throws InterruptedException
+ {
+ synchronized (this)
+ {
+ for (Thread thread : _acceptors)
+ if (thread != null)
+ thread.join(timeout);
+ }
+ }
+
+ protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Is the connector accepting new connections
+ */
+ protected boolean isAccepting()
+ {
+ return isRunning();
+ }
+
+ @Override
+ public ConnectionFactory getConnectionFactory(String protocol)
+ {
+ synchronized (_factories)
+ {
+ return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
+ }
+ }
+
+ @Override
+ public T getConnectionFactory(Class factoryType)
+ {
+ synchronized (_factories)
+ {
+ for (ConnectionFactory f : _factories.values())
+ if (factoryType.isAssignableFrom(f.getClass()))
+ return (T)f;
+ return null;
+ }
+ }
+
+ public void addConnectionFactory(ConnectionFactory factory)
+ {
+ synchronized (_factories)
+ {
+ ConnectionFactory old=_factories.remove(factory.getProtocol());
+ if (old!=null)
+ removeBean(old);
+ _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
+ addBean(factory);
+ if (_defaultProtocol==null)
+ _defaultProtocol=factory.getProtocol();
+ }
+ }
+
+ public ConnectionFactory removeConnectionFactory(String protocol)
+ {
+ synchronized (_factories)
+ {
+ ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
+ removeBean(factory);
+ return factory;
+ }
+ }
+
+ @Override
+ public Collection getConnectionFactories()
+ {
+ synchronized (_factories)
+ {
+ return _factories.values();
+ }
+ }
+
+ public void setConnectionFactories(Collection factories)
+ {
+ synchronized (_factories)
+ {
+ List existing = new ArrayList<>(_factories.values());
+ for (ConnectionFactory factory: existing)
+ removeConnectionFactory(factory.getProtocol());
+ for (ConnectionFactory factory: factories)
+ if (factory!=null)
+ addConnectionFactory(factory);
+ }
+ }
+
+
+ @Override
+ @ManagedAttribute("Protocols supported by this connector")
+ public List getProtocols()
+ {
+ synchronized (_factories)
+ {
+ return new ArrayList<>(_factories.keySet());
+ }
+ }
+
+ public void clearConnectionFactories()
+ {
+ synchronized (_factories)
+ {
+ _factories.clear();
+ }
+ }
+
+ @ManagedAttribute("This connector's default protocol")
+ public String getDefaultProtocol()
+ {
+ return _defaultProtocol;
+ }
+
+ public void setDefaultProtocol(String defaultProtocol)
+ {
+ _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
+ if (isRunning())
+ _defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
+ }
+
+ @Override
+ public ConnectionFactory getDefaultConnectionFactory()
+ {
+ if (isStarted())
+ return _defaultConnectionFactory;
+ return getConnectionFactory(_defaultProtocol);
+ }
+
+ private class Acceptor implements Runnable
+ {
+ private final int _acceptor;
+
+ private Acceptor(int id)
+ {
+ _acceptor = id;
+ }
+
+ @Override
+ public void run()
+ {
+ Thread current = Thread.currentThread();
+ String name = current.getName();
+ current.setName(name + "-acceptor-" + _acceptor + "-" + AbstractConnector.this);
+
+ synchronized (AbstractConnector.this)
+ {
+ _acceptors[_acceptor] = current;
+ }
+
+ try
+ {
+ while (isAccepting())
+ {
+ try
+ {
+ accept(_acceptor);
+ }
+ catch (Throwable e)
+ {
+ if (isAccepting())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+ }
+ }
+ }
+ finally
+ {
+ current.setName(name);
+
+ synchronized (AbstractConnector.this)
+ {
+ _acceptors[_acceptor] = null;
+ }
+ CountDownLatch stopping=_stopping;
+ if (stopping!=null)
+ stopping.countDown();
+ }
+ }
+ }
+
+
+
+
+// protected void connectionOpened(Connection connection)
+// {
+// _stats.connectionOpened();
+// connection.onOpen();
+// }
+//
+// protected void connectionClosed(Connection connection)
+// {
+// connection.onClose();
+// long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp();
+// _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut());
+// }
+//
+// public void connectionUpgraded(Connection oldConnection, Connection newConnection)
+// {
+// oldConnection.onClose();
+// _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut());
+// newConnection.onOpen();
+// }
+
+ @Override
+ public Collection getConnectedEndPoints()
+ {
+ return _immutableEndPoints;
+ }
+
+ protected void onEndPointOpened(EndPoint endp)
+ {
+ _endpoints.add(endp);
+ }
+
+ protected void onEndPointClosed(EndPoint endp)
+ {
+ _endpoints.remove(endp);
+ }
+
+ @Override
+ public Scheduler getScheduler()
+ {
+ return _scheduler;
+ }
+
+ @Override
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set a connector name. A context may be configured with
+ * virtual hosts in the form "@contextname" and will only serve
+ * requests from the named connector,
+ * @param name A connector name.
+ */
+ public void setName(String name)
+ {
+ _name=name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",
+ _name==null?getClass().getSimpleName():_name,
+ hashCode(),
+ getDefaultProtocol());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java
new file mode 100644
index 00000000..dcaecbe5
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -0,0 +1,483 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard NCSA common log format.
+ * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) and the
+ * Combined Log Format (single log format). This log format can be output by most web servers, and almost all web log
+ * analysis software can understand these formats.
+ */
+public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+ protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class);
+
+ private static ThreadLocal _buffers = new ThreadLocal()
+ {
+ @Override
+ protected StringBuilder initialValue()
+ {
+ return new StringBuilder(256);
+ }
+ };
+
+
+ private String[] _ignorePaths;
+ private boolean _extended;
+ private transient PathMap _ignorePathMap;
+ private boolean _logLatency = false;
+ private boolean _logCookies = false;
+ private boolean _logServer = false;
+ private boolean _preferProxiedForAddress;
+ private transient DateCache _logDateCache;
+ private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+ private Locale _logLocale = Locale.getDefault();
+ private String _logTimeZone = "GMT";
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Is logging enabled
+ */
+ protected abstract boolean isEnabled();
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Write requestEntry out. (to disk or slf4j log)
+ */
+ public abstract void write(String requestEntry) throws IOException;
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Writes the request and response information to the output stream.
+ *
+ * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request,
+ * org.eclipse.jetty.server.Response)
+ */
+ @Override
+ public void log(Request request, Response response)
+ {
+ try
+ {
+ if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+ return;
+
+ if (!isEnabled())
+ return;
+
+ StringBuilder buf = _buffers.get();
+ buf.setLength(0);
+
+ if (_logServer)
+ {
+ buf.append(request.getServerName());
+ buf.append(' ');
+ }
+
+ String addr = null;
+ if (_preferProxiedForAddress)
+ {
+ addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString());
+ }
+
+ if (addr == null)
+ addr = request.getRemoteAddr();
+
+ buf.append(addr);
+ buf.append(" - ");
+ Authentication authentication = request.getAuthentication();
+ if (authentication instanceof Authentication.User)
+ buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+ else
+ buf.append(" - ");
+
+ buf.append(" [");
+ if (_logDateCache != null)
+ buf.append(_logDateCache.format(request.getTimeStamp()));
+ else
+ buf.append(request.getTimeStamp());
+
+ buf.append("] \"");
+ buf.append(request.getMethod());
+ buf.append(' ');
+ buf.append(request.getUri().toString());
+ buf.append(' ');
+ buf.append(request.getProtocol());
+ buf.append("\" ");
+
+ int status = response.getStatus();
+ if (status <= 0)
+ status = 404;
+ buf.append((char)('0' + ((status / 100) % 10)));
+ buf.append((char)('0' + ((status / 10) % 10)));
+ buf.append((char)('0' + (status % 10)));
+
+ long responseLength = response.getLongContentLength();
+ if (responseLength >= 0)
+ {
+ buf.append(' ');
+ if (responseLength > 99999)
+ buf.append(responseLength);
+ else
+ {
+ if (responseLength > 9999)
+ buf.append((char)('0' + ((responseLength / 10000) % 10)));
+ if (responseLength > 999)
+ buf.append((char)('0' + ((responseLength / 1000) % 10)));
+ if (responseLength > 99)
+ buf.append((char)('0' + ((responseLength / 100) % 10)));
+ if (responseLength > 9)
+ buf.append((char)('0' + ((responseLength / 10) % 10)));
+ buf.append((char)('0' + (responseLength) % 10));
+ }
+ buf.append(' ');
+ }
+ else
+ buf.append(" - ");
+
+
+ if (_extended)
+ logExtended(request, response, buf);
+
+ if (_logCookies)
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null || cookies.length == 0)
+ buf.append(" -");
+ else
+ {
+ buf.append(" \"");
+ for (int i = 0; i < cookies.length; i++)
+ {
+ if (i != 0)
+ buf.append(';');
+ buf.append(cookies[i].getName());
+ buf.append('=');
+ buf.append(cookies[i].getValue());
+ }
+ buf.append('\"');
+ }
+ }
+
+ if (_logLatency)
+ {
+ long now = System.currentTimeMillis();
+
+ if (_logLatency)
+ {
+ buf.append(' ');
+ buf.append(now - request.getTimeStamp());
+ }
+ }
+
+ String log = buf.toString();
+ write(log);
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Writes extended request and response information to the output stream.
+ *
+ * @param request request object
+ * @param response response object
+ * @param b StringBuilder to write to
+ * @throws IOException
+ */
+ protected void logExtended(Request request,
+ Response response,
+ StringBuilder b) throws IOException
+ {
+ String referer = request.getHeader(HttpHeader.REFERER.toString());
+ if (referer == null)
+ b.append("\"-\" ");
+ else
+ {
+ b.append('"');
+ b.append(referer);
+ b.append("\" ");
+ }
+
+ String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
+ if (agent == null)
+ b.append("\"-\" ");
+ else
+ {
+ b.append('"');
+ b.append(agent);
+ b.append('"');
+ }
+ }
+
+
+ /**
+ * Set request paths that will not be logged.
+ *
+ * @param ignorePaths array of request paths
+ */
+ public void setIgnorePaths(String[] ignorePaths)
+ {
+ _ignorePaths = ignorePaths;
+ }
+
+ /**
+ * Retrieve the request paths that will not be logged.
+ *
+ * @return array of request paths
+ */
+ public String[] getIgnorePaths()
+ {
+ return _ignorePaths;
+ }
+
+ /**
+ * Controls logging of the request cookies.
+ *
+ * @param logCookies true - values of request cookies will be logged, false - values of request cookies will not be
+ * logged
+ */
+ public void setLogCookies(boolean logCookies)
+ {
+ _logCookies = logCookies;
+ }
+
+ /**
+ * Retrieve log cookies flag
+ *
+ * @return value of the flag
+ */
+ public boolean getLogCookies()
+ {
+ return _logCookies;
+ }
+
+ /**
+ * Controls logging of the request hostname.
+ *
+ * @param logServer true - request hostname will be logged, false - request hostname will not be logged
+ */
+ public void setLogServer(boolean logServer)
+ {
+ _logServer = logServer;
+ }
+
+ /**
+ * Retrieve log hostname flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getLogServer()
+ {
+ return _logServer;
+ }
+
+ /**
+ * Controls logging of request processing time.
+ *
+ * @param logLatency true - request processing time will be logged false - request processing time will not be
+ * logged
+ */
+ public void setLogLatency(boolean logLatency)
+ {
+ _logLatency = logLatency;
+ }
+
+ /**
+ * Retrieve log request processing time flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getLogLatency()
+ {
+ return _logLatency;
+ }
+
+ /**
+ * @deprecated use {@link StatisticsHandler}
+ */
+ public void setLogDispatch(boolean value)
+ {
+ }
+
+ /**
+ * @deprecated use {@link StatisticsHandler}
+ */
+ public boolean isLogDispatch()
+ {
+ return false;
+ }
+
+ /**
+ * Controls whether the actual IP address of the connection or the IP address from the X-Forwarded-For header will
+ * be logged.
+ *
+ * @param preferProxiedForAddress true - IP address from header will be logged, false - IP address from the
+ * connection will be logged
+ */
+ public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+ {
+ _preferProxiedForAddress = preferProxiedForAddress;
+ }
+
+ /**
+ * Retrieved log X-Forwarded-For IP address flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getPreferProxiedForAddress()
+ {
+ return _preferProxiedForAddress;
+ }
+
+ /**
+ * Set the extended request log format flag.
+ *
+ * @param extended true - log the extended request information, false - do not log the extended request information
+ */
+ public void setExtended(boolean extended)
+ {
+ _extended = extended;
+ }
+
+ /**
+ * Retrieve the extended request log format flag.
+ *
+ * @return value of the flag
+ */
+ @ManagedAttribute("use extended NCSA format")
+ public boolean isExtended()
+ {
+ return _extended;
+ }
+
+ /**
+ * Set up request logging and open log file.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ if (_logDateFormat != null)
+ {
+ _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
+ }
+
+ if (_ignorePaths != null && _ignorePaths.length > 0)
+ {
+ _ignorePathMap = new PathMap<>();
+ for (int i = 0; i < _ignorePaths.length; i++)
+ _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
+ }
+ else
+ _ignorePathMap = null;
+
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _logDateCache = null;
+ super.doStop();
+ }
+
+ /**
+ * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request
+ * timestamp is used.
+ *
+ * @param format timestamp format string
+ */
+ public void setLogDateFormat(String format)
+ {
+ _logDateFormat = format;
+ }
+
+ /**
+ * Retrieve the timestamp format string for request log entries.
+ *
+ * @return timestamp format string.
+ */
+ public String getLogDateFormat()
+ {
+ return _logDateFormat;
+ }
+
+ /**
+ * Set the locale of the request log.
+ *
+ * @param logLocale locale object
+ */
+ public void setLogLocale(Locale logLocale)
+ {
+ _logLocale = logLocale;
+ }
+
+ /**
+ * Retrieve the locale of the request log.
+ *
+ * @return locale object
+ */
+ public Locale getLogLocale()
+ {
+ return _logLocale;
+ }
+
+ /**
+ * Set the timezone of the request log.
+ *
+ * @param tz timezone string
+ */
+ public void setLogTimeZone(String tz)
+ {
+ _logTimeZone = tz;
+ }
+
+ /**
+ * Retrieve the timezone of the request log.
+ *
+ * @return timezone string
+ */
+ @ManagedAttribute("the timezone")
+ public String getLogTimeZone()
+ {
+ return _logTimeZone;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java
new file mode 100644
index 00000000..08b65bc4
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java
@@ -0,0 +1,125 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract Network Connector.
+ *
+ * Extends the {@link AbstractConnector} support for the {@link NetworkConnector} interface.
+ */
+@ManagedObject("AbstractNetworkConnector")
+public abstract class AbstractNetworkConnector extends AbstractConnector implements NetworkConnector
+{
+
+ private volatile String _host;
+ private volatile int _port = 0;
+
+ public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+ {
+ super(server,executor,scheduler,pool,acceptors,factories);
+ }
+
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ @Override
+ @ManagedAttribute("The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0, then bind to all interfaces.")
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setPort(int port)
+ {
+ _port = port;
+ }
+
+ @Override
+ @ManagedAttribute("Port this connector listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()")
+ public int getPort()
+ {
+ return _port;
+ }
+
+ @Override
+ public int getLocalPort()
+ {
+ return -1;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ open();
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ close();
+ super.doStop();
+ }
+
+ @Override
+ public void open() throws IOException
+ {
+ }
+
+ @Override
+ public void close()
+ {
+ // Interrupting is often sufficient to close the channel
+ interruptAcceptors();
+ }
+
+
+ @Override
+ public Future shutdown()
+ {
+ close();
+ return super.shutdown();
+ }
+
+ @Override
+ protected boolean isAccepting()
+ {
+ return super.isAccepting() && isOpen();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s{%s:%d}",
+ super.toString(),
+ getHost() == null ? "0.0.0.0" : getHost(),
+ getLocalPort() <= 0 ? getPort() : getLocalPort());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java
new file mode 100644
index 00000000..cc0eec89
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent implements Runnable
+{
+ final private Context _context;
+ final private AsyncContextState _asyncContext;
+ private volatile HttpChannelState _state;
+ private ServletContext _dispatchContext;
+ private String _dispatchPath;
+ private volatile Scheduler.Task _timeoutTask;
+ private Throwable _throwable;
+
+ public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+ {
+ super(null,request,response,null);
+ _context=context;
+ _asyncContext=asyncContext;
+ _state=state;
+
+ // If we haven't been async dispatched before
+ if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+ {
+ // We are setting these attributes during startAsync, when the spec implies that
+ // they are only available after a call to AsyncContext.dispatch(...);
+
+ // have we been forwarded before?
+ String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+ if (uri!=null)
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+ }
+ else
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+ }
+ }
+ }
+
+ public ServletContext getSuspendedContext()
+ {
+ return _context;
+ }
+
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ public ServletContext getDispatchContext()
+ {
+ return _dispatchContext;
+ }
+
+ public ServletContext getServletContext()
+ {
+ return _dispatchContext==null?_context:_dispatchContext;
+ }
+
+ /**
+ * @return The path in the context
+ */
+ public String getPath()
+ {
+ return _dispatchPath;
+ }
+
+ public void setTimeoutTask(Scheduler.Task task)
+ {
+ _timeoutTask = task;
+ }
+
+ public void cancelTimeoutTask()
+ {
+ Scheduler.Task task=_timeoutTask;
+ _timeoutTask=null;
+ if (task!=null)
+ task.cancel();
+ }
+
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ return _asyncContext;
+ }
+
+ @Override
+ public Throwable getThrowable()
+ {
+ return _throwable;
+ }
+
+ public void setThrowable(Throwable throwable)
+ {
+ _throwable=throwable;
+ }
+
+ public void setDispatchContext(ServletContext context)
+ {
+ _dispatchContext=context;
+ }
+
+ public void setDispatchPath(String path)
+ {
+ _dispatchPath=path;
+ }
+
+ public void completed()
+ {
+ _timeoutTask=null;
+ _asyncContext.reset();
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return _state;
+ }
+
+ @Override
+ public void run()
+ {
+ Scheduler.Task task=_timeoutTask;
+ _timeoutTask=null;
+ if (task!=null)
+ _state.expired();
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java
new file mode 100644
index 00000000..6503424d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java
@@ -0,0 +1,185 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+
+public class AsyncContextState implements AsyncContext
+{
+ volatile HttpChannelState _state;
+
+ public AsyncContextState(HttpChannelState state)
+ {
+ _state=state;
+ }
+
+ HttpChannelState state()
+ {
+ HttpChannelState state=_state;
+ if (state==null)
+ throw new IllegalStateException("AsyncContext completed");
+ return state;
+ }
+
+ @Override
+ public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+ {
+ AsyncListener wrap = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+ };
+ state().addListener(wrap);
+ }
+
+ @Override
+ public void addListener(AsyncListener listener)
+ {
+ state().addListener(listener);
+ }
+
+ @Override
+ public void complete()
+ {
+ state().complete();
+ }
+
+ @Override
+ public T createListener(Class clazz) throws ServletException
+ {
+ ContextHandler contextHandler = state().getContextHandler();
+ if (contextHandler != null)
+ return contextHandler.getServletContext().createListener(clazz);
+ try
+ {
+ return clazz.newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public void dispatch()
+ {
+ state().dispatch(null,null);
+ }
+
+ @Override
+ public void dispatch(String path)
+ {
+ state().dispatch(null,path);
+ }
+
+ @Override
+ public void dispatch(ServletContext context, String path)
+ {
+ state().dispatch(context,path);
+ }
+
+ @Override
+ public ServletRequest getRequest()
+ {
+ return state().getAsyncContextEvent().getSuppliedRequest();
+ }
+
+ @Override
+ public ServletResponse getResponse()
+ {
+ return state().getAsyncContextEvent().getSuppliedResponse();
+ }
+
+ @Override
+ public long getTimeout()
+ {
+ return state().getTimeout();
+ }
+
+ @Override
+ public boolean hasOriginalRequestAndResponse()
+ {
+ HttpChannel> channel=state().getHttpChannel();
+ return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+ }
+
+ @Override
+ public void setTimeout(long arg0)
+ {
+ state().setTimeout(arg0);
+ }
+
+ @Override
+ public void start(final Runnable task)
+ {
+ state().getHttpChannel().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+ }
+ });
+ }
+
+ public void reset()
+ {
+ _state=null;
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return state();
+ }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java
new file mode 100644
index 00000000..33ba21d7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java
@@ -0,0 +1,129 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * An asynchronously writing NCSA Request Log
+ */
+public class AsyncNCSARequestLog extends NCSARequestLog
+{
+ private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class);
+ private final BlockingQueue _queue;
+ private transient WriterThread _thread;
+ private boolean _warnedFull;
+
+ public AsyncNCSARequestLog()
+ {
+ this(null,null);
+ }
+
+ public AsyncNCSARequestLog(BlockingQueue queue)
+ {
+ this(null,queue);
+ }
+
+ public AsyncNCSARequestLog(String filename)
+ {
+ this(filename,null);
+ }
+
+ public AsyncNCSARequestLog(String filename,BlockingQueue queue)
+ {
+ super(filename);
+ if (queue==null)
+ queue=new BlockingArrayQueue<>(1024);
+ _queue=queue;
+ }
+
+ private class WriterThread extends Thread
+ {
+ WriterThread()
+ {
+ setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
+ }
+
+ @Override
+ public void run()
+ {
+ while (isRunning())
+ {
+ try
+ {
+ String log = _queue.poll(10,TimeUnit.SECONDS);
+ if (log!=null)
+ AsyncNCSARequestLog.super.write(log);
+
+ while(!_queue.isEmpty())
+ {
+ log=_queue.poll();
+ if (log!=null)
+ AsyncNCSARequestLog.super.write(log);
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ catch (InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ super.doStart();
+ _thread = new WriterThread();
+ _thread.start();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _thread.interrupt();
+ _thread.join();
+ super.doStop();
+ _thread=null;
+ }
+
+ @Override
+ public void write(String log) throws IOException
+ {
+ if (!_queue.offer(log))
+ {
+ if (_warnedFull)
+ LOG.warn("Log Queue overflow");
+ _warnedFull=true;
+ }
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Authentication.java b/lib/jetty/org/eclipse/jetty/server/Authentication.java
new file mode 100644
index 00000000..ccdf4c0f
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Authentication.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** The Authentication state of a request.
+ *
+ * The Authentication state can be one of several sub-types that
+ * reflects where the request is in the many different authentication
+ * cycles. Authentication might not yet be checked or it might be checked
+ * and failed, checked and deferred or succeeded.
+ *
+ */
+public interface Authentication
+{
+ /* ------------------------------------------------------------ */
+ public static class Failed extends QuietServletException
+ {
+ public Failed(String message)
+ {
+ super(message);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A successful Authentication with User information.
+ */
+ public interface User extends Authentication
+ {
+ String getAuthMethod();
+ UserIdentity getUserIdentity();
+ boolean isUserInRole(UserIdentity.Scope scope,String role);
+ void logout();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A wrapped authentication with methods provide the
+ * wrapped request/response for use by the application
+ */
+ public interface Wrapped extends Authentication
+ {
+ HttpServletRequest getHttpServletRequest();
+ HttpServletResponse getHttpServletResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A deferred authentication with methods to progress
+ * the authentication process.
+ */
+ public interface Deferred extends Authentication
+ {
+ /* ------------------------------------------------------------ */
+ /** Authenticate if possible without sending a challenge.
+ * This is used to check credentials that have been sent for
+ * non-manditory authentication.
+ * @return The new Authentication state.
+ */
+ Authentication authenticate(ServletRequest request);
+
+ /* ------------------------------------------------------------ */
+ /** Authenticate and possibly send a challenge.
+ * This is used to initiate authentication for previously
+ * non-manditory authentication.
+ * @return The new Authentication state.
+ */
+ Authentication authenticate(ServletRequest request,ServletResponse response);
+
+
+ /* ------------------------------------------------------------ */
+ /** Login with the LOGIN authenticator
+ * @param username
+ * @param password
+ * @return The new Authentication state
+ */
+ Authentication login(String username,Object password,ServletRequest request);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Authentication Response sent state.
+ * Responses are sent by authenticators either to issue an
+ * authentication challenge or on successful authentication in
+ * order to redirect the user to the original URL.
+ */
+ public interface ResponseSent extends Authentication
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** An Authentication Challenge has been sent.
+ */
+ public interface Challenge extends ResponseSent
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** An Authentication Failure has been sent.
+ */
+ public interface Failure extends ResponseSent
+ {
+ }
+
+ public interface SendSuccess extends ResponseSent
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Unauthenticated state.
+ *
+ * This convenience instance is for non mandatory authentication where credentials
+ * have been presented and checked, but failed authentication.
+ */
+ public final static Authentication UNAUTHENTICATED = new Authentication(){@Override
+ public String toString(){return "UNAUTHENTICATED";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication not checked
+ *
+ * This convenience instance us for non mandatory authentication when no
+ * credentials are present to be checked.
+ */
+ public final static Authentication NOT_CHECKED = new Authentication(){@Override
+ public String toString(){return "NOT CHECKED";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication challenge sent.
+ *
+ * This convenience instance is for when an authentication challenge has been sent.
+ */
+ public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override
+ public String toString(){return "CHALLENGE";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication failure sent.
+ *
+ * This convenience instance is for when an authentication failure has been sent.
+ */
+ public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override
+ public String toString(){return "FAILURE";}};
+ public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override
+ public String toString(){return "SEND_SUCCESS";}};
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
new file mode 100644
index 00000000..cd2d12e8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ *
An implementation of HttpInput using {@link ByteBuffer} as items.
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput
+{
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ protected void onContentConsumed(ByteBuffer item)
+ {
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java b/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java
new file mode 100644
index 00000000..466775e4
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+public class ClassLoaderDump implements Dumpable
+{
+ final ClassLoader _loader;
+
+ public ClassLoaderDump(ClassLoader loader)
+ {
+ _loader = loader;
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ if (_loader==null)
+ out.append("No ClassLoader\n");
+ else
+ {
+ out.append(String.valueOf(_loader)).append("\n");
+
+ Object parent = _loader.getParent();
+ if (parent != null)
+ {
+ if (!(parent instanceof Dumpable))
+ parent = new ClassLoaderDump((ClassLoader)parent);
+
+ if (_loader instanceof URLClassLoader)
+ ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+ else
+ ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java
new file mode 100644
index 00000000..e23739d8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+/**
+ * A Factory to create {@link Connection} instances for {@link Connector}s.
+ * A Connection factory is responsible for instantiating and configuring a {@link Connection} instance
+ * to handle an {@link EndPoint} accepted by a {@link Connector}.
+ *
+ * A ConnectionFactory has a protocol name that represents the protocol of the Connections
+ * created. Example of protocol names include:
+ * http Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1
+ * spdy/2 Creates a HTTP connection that handles a specific version of the SPDY protocol
+ * SSL-XYZ Create an SSL connection chained to a connection obtained from a connection factory
+ * with a protocol "XYZ".
+ * SSL-http Create an SSL connection chained to a HTTP connection (aka https)
+ * SSL-npn Create an SSL connection chained to a NPN connection, that uses a negotiation with
+ * the client to determine the next protocol.
+ *
+ */
+public interface ConnectionFactory
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A string representing the protocol name.
+ */
+ public String getProtocol();
+
+ /**
+ * Creates a new {@link Connection} with the given parameters
+ * @param connector The {@link Connector} creating this connection
+ * @param endPoint the {@link EndPoint} associated with the connection
+ * @return a new {@link Connection}
+ */
+ public Connection newConnection(Connector connector, EndPoint endPoint);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Connector.java b/lib/jetty/org/eclipse/jetty/server/Connector.java
new file mode 100644
index 00000000..ce4544c9
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Connector.java
@@ -0,0 +1,105 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * A {@link Connector} accept connections and data from remote peers,
+ * and allows applications to send data to remote peers, by setting up
+ * the machinery needed to handle such tasks.
+ */
+@ManagedObject("Connector Interface")
+public interface Connector extends LifeCycle, Graceful
+{
+ /**
+ * @return the {@link Server} instance associated with this {@link Connector}
+ */
+ public Server getServer();
+
+ /**
+ * @return the {@link Executor} used to submit tasks
+ */
+ public Executor getExecutor();
+
+ /**
+ * @return the {@link Scheduler} used to schedule tasks
+ */
+ public Scheduler getScheduler();
+
+ /**
+ * @return the {@link ByteBufferPool} to acquire buffers from and release buffers to
+ */
+ public ByteBufferPool getByteBufferPool();
+
+ /**
+ * @return the {@link ConnectionFactory} associated with the protocol name
+ */
+ public ConnectionFactory getConnectionFactory(String nextProtocol);
+
+
+ public T getConnectionFactory(Class factoryType);
+
+ /**
+ * @return the default {@link ConnectionFactory} associated with the default protocol name
+ */
+ public ConnectionFactory getDefaultConnectionFactory();
+
+ public Collection getConnectionFactories();
+
+ public List getProtocols();
+
+ /**
+ * @return the max idle timeout for connections in milliseconds
+ */
+ @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+ public long getIdleTimeout();
+
+ /**
+ * @return the underlying socket, channel, buffer etc. for the connector.
+ */
+ public Object getTransport();
+
+ /**
+ * @return immutable collection of connected endpoints
+ */
+ public Collection getConnectedEndPoints();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the connector name if set.
+ * A {@link ContextHandler} may be configured with
+ * virtual hosts in the form "@connectorName" and will only serve
+ * requests from the named connector.
+ * @return The connector name or null.
+ */
+ public String getName();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java b/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java
new file mode 100644
index 00000000..cae5e6eb
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java
@@ -0,0 +1,308 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)}
+ * will register the listener with all connections accepted by that connector.
+ */
+@ManagedObject("Connector Statistics")
+public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
+{
+ private final static Sample ZERO=new Sample();
+ private final AtomicLong _startMillis = new AtomicLong(-1L);
+ private final CounterStatistic _connectionStats = new CounterStatistic();
+ private final SampleStatistic _messagesIn = new SampleStatistic();
+ private final SampleStatistic _messagesOut = new SampleStatistic();
+ private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+ private final ConcurrentMap _samples = new ConcurrentHashMap<>();
+ private final AtomicInteger _closedIn = new AtomicInteger();
+ private final AtomicInteger _closedOut = new AtomicInteger();
+ private AtomicLong _nanoStamp=new AtomicLong();
+ private volatile int _messagesInPerSecond;
+ private volatile int _messagesOutPerSecond;
+
+ @Override
+ public void onOpened(Connection connection)
+ {
+ if (isStarted())
+ {
+ _connectionStats.increment();
+ _samples.put(connection,ZERO);
+ }
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ if (isStarted())
+ {
+ int msgsIn=connection.getMessagesIn();
+ int msgsOut=connection.getMessagesOut();
+ _messagesIn.set(msgsIn);
+ _messagesOut.set(msgsOut);
+ _connectionStats.decrement();
+ _connectionDurationStats.set(System.currentTimeMillis()-connection.getCreatedTimeStamp());
+
+ Sample sample=_samples.remove(connection);
+ if (sample!=null)
+ {
+ _closedIn.addAndGet(msgsIn-sample._messagesIn);
+ _closedOut.addAndGet(msgsOut-sample._messagesOut);
+ }
+ }
+ }
+
+ @ManagedAttribute("Total number of bytes received by this connector")
+ public int getBytesIn()
+ {
+ // TODO
+ return -1;
+ }
+
+ @ManagedAttribute("Total number of bytes sent by this connector")
+ public int getBytesOut()
+ {
+ // TODO
+ return -1;
+ }
+
+ @ManagedAttribute("Total number of connections seen by this connector")
+ public int getConnections()
+ {
+ return (int)_connectionStats.getTotal();
+ }
+
+ @ManagedAttribute("Connection duration maximum in ms")
+ public long getConnectionDurationMax()
+ {
+ return _connectionDurationStats.getMax();
+ }
+
+ @ManagedAttribute("Connection duration mean in ms")
+ public double getConnectionDurationMean()
+ {
+ return _connectionDurationStats.getMean();
+ }
+
+ @ManagedAttribute("Connection duration standard deviation")
+ public double getConnectionDurationStdDev()
+ {
+ return _connectionDurationStats.getStdDev();
+ }
+
+ @ManagedAttribute("Messages In for all connections")
+ public int getMessagesIn()
+ {
+ return (int)_messagesIn.getTotal();
+ }
+
+ @ManagedAttribute("Messages In per connection maximum")
+ public int getMessagesInPerConnectionMax()
+ {
+ return (int)_messagesIn.getMax();
+ }
+
+ @ManagedAttribute("Messages In per connection mean")
+ public double getMessagesInPerConnectionMean()
+ {
+ return _messagesIn.getMean();
+ }
+
+ @ManagedAttribute("Messages In per connection standard deviation")
+ public double getMessagesInPerConnectionStdDev()
+ {
+ return _messagesIn.getStdDev();
+ }
+
+ @ManagedAttribute("Connections open")
+ public int getConnectionsOpen()
+ {
+ return (int)_connectionStats.getCurrent();
+ }
+
+ @ManagedAttribute("Connections open maximum")
+ public int getConnectionsOpenMax()
+ {
+ return (int)_connectionStats.getMax();
+ }
+
+ @ManagedAttribute("Messages Out for all connections")
+ public int getMessagesOut()
+ {
+ return (int)_messagesIn.getTotal();
+ }
+
+ @ManagedAttribute("Messages In per connection maximum")
+ public int getMessagesOutPerConnectionMax()
+ {
+ return (int)_messagesIn.getMax();
+ }
+
+ @ManagedAttribute("Messages In per connection mean")
+ public double getMessagesOutPerConnectionMean()
+ {
+ return _messagesIn.getMean();
+ }
+
+ @ManagedAttribute("Messages In per connection standard deviation")
+ public double getMessagesOutPerConnectionStdDev()
+ {
+ return _messagesIn.getStdDev();
+ }
+
+ @ManagedAttribute("Connection statistics started ms since epoch")
+ public long getStartedMillis()
+ {
+ long start = _startMillis.get();
+ return start < 0 ? 0 : System.currentTimeMillis() - start;
+ }
+
+ @ManagedAttribute("Messages in per second calculated over period since last called")
+ public int getMessagesInPerSecond()
+ {
+ update();
+ return _messagesInPerSecond;
+ }
+
+ @ManagedAttribute("Messages out per second calculated over period since last called")
+ public int getMessagesOutPerSecond()
+ {
+ update();
+ return _messagesOutPerSecond;
+ }
+
+ @Override
+ public void doStart()
+ {
+ reset();
+ }
+
+ @Override
+ public void doStop()
+ {
+ _samples.clear();
+ }
+
+ @ManagedOperation("Reset the statistics")
+ public void reset()
+ {
+ _startMillis.set(System.currentTimeMillis());
+ _messagesIn.reset();
+ _messagesOut.reset();
+ _connectionStats.reset();
+ _connectionDurationStats.reset();
+ _samples.clear();
+ }
+
+ @Override
+ @ManagedOperation("dump thread state")
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dumpObject(out,this);
+ ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
+ }
+
+ public static void addToAllConnectors(Server server)
+ {
+ for (Connector connector : server.getConnectors())
+ {
+ if (connector instanceof Container)
+ ((Container)connector).addBean(new ConnectorStatistics());
+ }
+ }
+
+ private static final long SECOND_NANOS=TimeUnit.SECONDS.toNanos(1);
+ private synchronized void update()
+ {
+ long now=System.nanoTime();
+ long then=_nanoStamp.get();
+ long duration=now-then;
+
+ if (duration>SECOND_NANOS/2)
+ {
+ if (_nanoStamp.compareAndSet(then,now))
+ {
+ long msgsIn=_closedIn.getAndSet(0);
+ long msgsOut=_closedOut.getAndSet(0);
+
+ for (Map.Entry entry : _samples.entrySet())
+ {
+ Connection connection=entry.getKey();
+ Sample sample = entry.getValue();
+ Sample next = new Sample(connection);
+ if (_samples.replace(connection,sample,next))
+ {
+ msgsIn+=next._messagesIn-sample._messagesIn;
+ msgsOut+=next._messagesOut-sample._messagesOut;
+ }
+ }
+
+ _messagesInPerSecond=(int)(msgsIn*SECOND_NANOS/duration);
+ _messagesOutPerSecond=(int)(msgsOut*SECOND_NANOS/duration);
+ }
+ }
+ }
+
+ private static class Sample
+ {
+ Sample()
+ {
+ _messagesIn=0;
+ _messagesOut=0;
+ }
+
+ Sample(Connection connection)
+ {
+ _messagesIn=connection.getMessagesIn();
+ _messagesOut=connection.getMessagesOut();
+ }
+
+ final int _messagesIn;
+ final int _messagesOut;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/CookieCutter.java b/lib/jetty/org/eclipse/jetty/server/CookieCutter.java
new file mode 100644
index 00000000..703f8049
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/CookieCutter.java
@@ -0,0 +1,328 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * Optimized stateful cookie parser. Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the
+ * cookies are not re parsed.
+ *
+ *
+ */
+public class CookieCutter
+{
+ private static final Logger LOG = Log.getLogger(CookieCutter.class);
+
+ private Cookie[] _cookies;
+ private Cookie[] _lastCookies;
+ private final List _fieldList = new ArrayList<>();
+ int _fields;
+
+ public CookieCutter()
+ {
+ }
+
+ public Cookie[] getCookies()
+ {
+ if (_cookies!=null)
+ return _cookies;
+
+ if (_lastCookies!=null && _fields==_fieldList.size())
+ _cookies=_lastCookies;
+ else
+ parseFields();
+ _lastCookies=_cookies;
+ return _cookies;
+ }
+
+ public void setCookies(Cookie[] cookies)
+ {
+ _cookies=cookies;
+ _lastCookies=null;
+ _fieldList.clear();
+ _fields=0;
+ }
+
+ public void reset()
+ {
+ _cookies=null;
+ _fields=0;
+ }
+
+ public void addCookieField(String f)
+ {
+ if (f==null)
+ return;
+ f=f.trim();
+ if (f.length()==0)
+ return;
+
+ if (_fieldList.size()>_fields)
+ {
+ if (f.equals(_fieldList.get(_fields)))
+ {
+ _fields++;
+ return;
+ }
+
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
+ }
+ _cookies=null;
+ _lastCookies=null;
+ _fieldList.add(_fields++,f);
+ }
+
+
+ protected void parseFields()
+ {
+ _lastCookies=null;
+ _cookies=null;
+
+ List cookies = new ArrayList<>();
+
+ int version = 0;
+
+ // delete excess fields
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
+
+ // For each cookie field
+ for (String hdr : _fieldList)
+ {
+ // Parse the header
+ String name = null;
+ String value = null;
+
+ Cookie cookie = null;
+
+ boolean invalue=false;
+ boolean quoted=false;
+ boolean escaped=false;
+ int tokenstart=-1;
+ int tokenend=-1;
+ for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
+ {
+ char c = hdr.charAt(i);
+
+ // Handle quoted values for name or value
+ if (quoted)
+ {
+ if (escaped)
+ {
+ escaped=false;
+ continue;
+ }
+
+ switch (c)
+ {
+ case '"':
+ tokenend=i;
+ quoted=false;
+
+ // handle quote as last character specially
+ if (i==last)
+ {
+ if (invalue)
+ value = hdr.substring(tokenstart, tokenend+1);
+ else
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ }
+ }
+ break;
+
+ case '\\':
+ escaped=true;
+ continue;
+ default:
+ continue;
+ }
+ }
+ else
+ {
+ // Handle name and value state machines
+ if (invalue)
+ {
+ // parse the value
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ continue;
+
+ case '"':
+ if (tokenstart<0)
+ {
+ quoted=true;
+ tokenstart=i;
+ }
+ tokenend=i;
+ if (i==last)
+ {
+ value = hdr.substring(tokenstart, tokenend+1);
+ break;
+ }
+ continue;
+
+ case ';':
+ if (tokenstart>=0)
+ value = hdr.substring(tokenstart, tokenend+1);
+ else
+ value="";
+ tokenstart = -1;
+ invalue=false;
+ break;
+
+ default:
+ if (tokenstart<0)
+ tokenstart=i;
+ tokenend=i;
+ if (i==last)
+ {
+ value = hdr.substring(tokenstart, tokenend+1);
+ break;
+ }
+ continue;
+ }
+ }
+ else
+ {
+ // parse the name
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ continue;
+
+ case '"':
+ if (tokenstart<0)
+ {
+ quoted=true;
+ tokenstart=i;
+ }
+ tokenend=i;
+ if (i==last)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ break;
+ }
+ continue;
+
+ case ';':
+ if (tokenstart>=0)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ }
+ tokenstart = -1;
+ break;
+
+ case '=':
+ if (tokenstart>=0)
+ name = hdr.substring(tokenstart, tokenend+1);
+ tokenstart = -1;
+ invalue=true;
+ continue;
+
+ default:
+ if (tokenstart<0)
+ tokenstart=i;
+ tokenend=i;
+ if (i==last)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ break;
+ }
+ continue;
+ }
+ }
+ }
+
+ // If after processing the current character we have a value and a name, then it is a cookie
+ if (value!=null && name!=null)
+ {
+ name=QuotedStringTokenizer.unquoteOnly(name);
+ value=QuotedStringTokenizer.unquoteOnly(value);
+
+ try
+ {
+ if (name.startsWith("$"))
+ {
+ String lowercaseName = name.toLowerCase(Locale.ENGLISH);
+ if ("$path".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setPath(value);
+ }
+ else if ("$domain".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setDomain(value);
+ }
+ else if ("$port".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setComment("$port="+value);
+ }
+ else if ("$version".equals(lowercaseName))
+ {
+ version = Integer.parseInt(value);
+ }
+ }
+ else
+ {
+ cookie = new Cookie(name, value);
+ if (version > 0)
+ cookie.setVersion(version);
+ cookies.add(cookie);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.debug(e);
+ }
+
+ name = null;
+ value = null;
+ }
+ }
+ }
+
+ _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
+ _lastCookies=_cookies;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Dispatcher.java b/lib/jetty/org/eclipse/jetty/server/Dispatcher.java
new file mode 100644
index 00000000..25672897
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Dispatcher.java
@@ -0,0 +1,456 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.MultiMap;
+
+public class Dispatcher implements RequestDispatcher
+{
+ /** Dispatch include attribute names */
+ public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+
+ /** Dispatch include attribute names */
+ public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+
+ private final ContextHandler _contextHandler;
+ private final String _uri;
+ private final String _path;
+ private final String _query;
+ private final String _named;
+
+ public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+ {
+ _contextHandler=contextHandler;
+ _uri=uri;
+ _path=pathInContext;
+ _query=query;
+ _named=null;
+ }
+
+ public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
+ {
+ _contextHandler=contextHandler;
+ _named=name;
+ _uri=null;
+ _path=null;
+ _query=null;
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.FORWARD);
+ }
+
+ public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.ERROR);
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+ if (!(request instanceof HttpServletRequest))
+ request = new ServletRequestHttpWrapper(request);
+ if (!(response instanceof HttpServletResponse))
+ response = new ServletResponseHttpWrapper(response);
+
+ final DispatcherType old_type = baseRequest.getDispatcherType();
+ final Attributes old_attr=baseRequest.getAttributes();
+ final MultiMap old_query_params=baseRequest.getQueryParameters();
+ try
+ {
+ baseRequest.setDispatcherType(DispatcherType.INCLUDE);
+ baseRequest.getResponse().include();
+ if (_named!=null)
+ {
+ _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ else
+ {
+ IncludeAttributes attr = new IncludeAttributes(old_attr);
+
+ attr._requestURI=_uri;
+ attr._contextPath=_contextHandler.getContextPath();
+ attr._servletPath=null; // set by ServletHandler
+ attr._pathInfo=_path;
+ attr._query=_query;
+
+ if (_query!=null)
+ baseRequest.mergeQueryParameters(_query, false);
+ baseRequest.setAttributes(attr);
+
+ _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ }
+ finally
+ {
+ baseRequest.setAttributes(old_attr);
+ baseRequest.getResponse().included();
+ baseRequest.setQueryParameters(old_query_params);
+ baseRequest.resetParameters();
+ baseRequest.setDispatcherType(old_type);
+ }
+ }
+
+ protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+ {
+ Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+ Response base_response=baseRequest.getResponse();
+ base_response.resetForForward();
+
+ if (!(request instanceof HttpServletRequest))
+ request = new ServletRequestHttpWrapper(request);
+ if (!(response instanceof HttpServletResponse))
+ response = new ServletResponseHttpWrapper(response);
+
+ final boolean old_handled=baseRequest.isHandled();
+ final String old_uri=baseRequest.getRequestURI();
+ final String old_context_path=baseRequest.getContextPath();
+ final String old_servlet_path=baseRequest.getServletPath();
+ final String old_path_info=baseRequest.getPathInfo();
+ final String old_query=baseRequest.getQueryString();
+ final MultiMap old_query_params=baseRequest.getQueryParameters();
+ final Attributes old_attr=baseRequest.getAttributes();
+ final DispatcherType old_type=baseRequest.getDispatcherType();
+
+ try
+ {
+ baseRequest.setHandled(false);
+ baseRequest.setDispatcherType(dispatch);
+
+ if (_named!=null)
+ {
+ _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ else
+ {
+ ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+ //If we have already been forwarded previously, then keep using the established
+ //original value. Otherwise, this is the first forward and we need to establish the values.
+ //Note: the established value on the original request for pathInfo and
+ //for queryString is allowed to be null, but cannot be null for the other values.
+ if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null)
+ {
+ attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO);
+ attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING);
+ attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI);
+ attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
+ attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
+ }
+ else
+ {
+ attr._pathInfo=old_path_info;
+ attr._query=old_query;
+ attr._requestURI=old_uri;
+ attr._contextPath=old_context_path;
+ attr._servletPath=old_servlet_path;
+ }
+
+ baseRequest.setRequestURI(_uri);
+ baseRequest.setContextPath(_contextHandler.getContextPath());
+ baseRequest.setServletPath(null);
+ baseRequest.setPathInfo(_uri);
+ if (_query!=null)
+ baseRequest.mergeQueryParameters(_query, true);
+ baseRequest.setAttributes(attr);
+
+ _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+ if (!baseRequest.getHttpChannelState().isAsync())
+ commitResponse(response,baseRequest);
+ }
+ }
+ finally
+ {
+ baseRequest.setHandled(old_handled);
+ baseRequest.setRequestURI(old_uri);
+ baseRequest.setContextPath(old_context_path);
+ baseRequest.setServletPath(old_servlet_path);
+ baseRequest.setPathInfo(old_path_info);
+ baseRequest.setQueryString(old_query);
+ baseRequest.setQueryParameters(old_query_params);
+ baseRequest.resetParameters();
+ baseRequest.setAttributes(old_attr);
+ baseRequest.setDispatcherType(old_type);
+ }
+ }
+
+ private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
+ {
+ if (baseRequest.getResponse().isWriting())
+ {
+ try
+ {
+ response.getWriter().close();
+ }
+ catch (IllegalStateException e)
+ {
+ response.getOutputStream().close();
+ }
+ }
+ else
+ {
+ try
+ {
+ response.getOutputStream().close();
+ }
+ catch (IllegalStateException e)
+ {
+ response.getWriter().close();
+ }
+ }
+ }
+
+ private class ForwardAttributes implements Attributes
+ {
+ final Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ ForwardAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object getAttribute(String key)
+ {
+ if (Dispatcher.this._named==null)
+ {
+ if (key.equals(FORWARD_PATH_INFO))
+ return _pathInfo;
+ if (key.equals(FORWARD_REQUEST_URI))
+ return _requestURI;
+ if (key.equals(FORWARD_SERVLET_PATH))
+ return _servletPath;
+ if (key.equals(FORWARD_CONTEXT_PATH))
+ return _contextPath;
+ if (key.equals(FORWARD_QUERY_STRING))
+ return _query;
+ }
+
+ if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+ return _attr.getAttribute(key);
+ }
+
+ @Override
+ public Enumeration getAttributeNames()
+ {
+ HashSet set=new HashSet<>();
+ Enumeration e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX) &&
+ !name.startsWith(__FORWARD_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(FORWARD_PATH_INFO);
+ else
+ set.remove(FORWARD_PATH_INFO);
+ set.add(FORWARD_REQUEST_URI);
+ set.add(FORWARD_SERVLET_PATH);
+ set.add(FORWARD_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(FORWARD_QUERY_STRING);
+ else
+ set.remove(FORWARD_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ @Override
+ public void setAttribute(String key, Object value)
+ {
+ if (_named==null && key.startsWith("javax.servlet."))
+ {
+ if (key.equals(FORWARD_PATH_INFO))
+ _pathInfo=(String)value;
+ else if (key.equals(FORWARD_REQUEST_URI))
+ _requestURI=(String)value;
+ else if (key.equals(FORWARD_SERVLET_PATH))
+ _servletPath=(String)value;
+ else if (key.equals(FORWARD_CONTEXT_PATH))
+ _contextPath=(String)value;
+ else if (key.equals(FORWARD_QUERY_STRING))
+ _query=(String)value;
+
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "FORWARD+"+_attr.toString();
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+
+ private class IncludeAttributes implements Attributes
+ {
+ final Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ IncludeAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ @Override
+ public Object getAttribute(String key)
+ {
+ if (Dispatcher.this._named==null)
+ {
+ if (key.equals(INCLUDE_PATH_INFO)) return _pathInfo;
+ if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath;
+ if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath;
+ if (key.equals(INCLUDE_QUERY_STRING)) return _query;
+ if (key.equals(INCLUDE_REQUEST_URI)) return _requestURI;
+ }
+ else if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+
+ return _attr.getAttribute(key);
+ }
+
+ @Override
+ public Enumeration getAttributeNames()
+ {
+ HashSet set=new HashSet<>();
+ Enumeration e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(INCLUDE_PATH_INFO);
+ else
+ set.remove(INCLUDE_PATH_INFO);
+ set.add(INCLUDE_REQUEST_URI);
+ set.add(INCLUDE_SERVLET_PATH);
+ set.add(INCLUDE_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(INCLUDE_QUERY_STRING);
+ else
+ set.remove(INCLUDE_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ @Override
+ public void setAttribute(String key, Object value)
+ {
+ if (_named==null && key.startsWith("javax.servlet."))
+ {
+ if (key.equals(INCLUDE_PATH_INFO)) _pathInfo=(String)value;
+ else if (key.equals(INCLUDE_REQUEST_URI)) _requestURI=(String)value;
+ else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
+ else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
+ else if (key.equals(INCLUDE_QUERY_STRING)) _query=(String)value;
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "INCLUDE+"+_attr.toString();
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java b/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java
new file mode 100644
index 00000000..88301d4b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class EncodingHttpWriter extends HttpWriter
+{
+ final Writer _converter;
+
+ /* ------------------------------------------------------------ */
+ public EncodingHttpWriter(HttpOutput out, String encoding)
+ {
+ super(out);
+ try
+ {
+ _converter = new OutputStreamWriter(_bytes, encoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+ if (length==0 && out.isAllContentWritten())
+ {
+ out.close();
+ return;
+ }
+
+ while (length > 0)
+ {
+ _bytes.reset();
+ int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+ _converter.write(s, offset, chars);
+ _converter.flush();
+ _bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
new file mode 100644
index 00000000..891e8ee1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
@@ -0,0 +1,291 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.net.InetSocketAddress;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.HttpConfiguration.Customizer;
+
+
+/* ------------------------------------------------------------ */
+/** Customize Requests for Proxy Forwarding.
+ *
+ * This customizer looks at at HTTP request for headers that indicate
+ * it has been forwarded by one or more proxies. Specifically handled are:
+ *
+ * X-Forwarded-Host
+ * X-Forwarded-Server
+ * X-Forwarded-For
+ * X-Forwarded-Proto
+ *
+ * If these headers are present, then the {@link Request} object is updated
+ * so that the proxy is not seen as the other end point of the connection on which
+ * the request came
+ * Headers can also be defined so that forwarded SSL Session IDs and Cipher
+ * suites may be customised
+ * @see Wikipedia: X-Forwarded-For
+ */
+public class ForwardedRequestCustomizer implements Customizer
+{
+ private String _hostHeader;
+ private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
+ private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
+ private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
+ private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
+ private String _forwardedCipherSuiteHeader;
+ private String _forwardedSslSessionIdHeader;
+
+
+ /* ------------------------------------------------------------ */
+ public String getHostHeader()
+ {
+ return _hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+ *
+ * @param hostHeader
+ * The value of the host header to force.
+ */
+ public void setHostHeader(String hostHeader)
+ {
+ _hostHeader = hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ *
+ * @see #setForwarded(boolean)
+ */
+ public String getForwardedHostHeader()
+ {
+ return _forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedHostHeader
+ * The header name for forwarded hosts (default x-forwarded-host)
+ */
+ public void setForwardedHostHeader(String forwardedHostHeader)
+ {
+ _forwardedHostHeader = forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the header name for forwarded server.
+ */
+ public String getForwardedServerHeader()
+ {
+ return _forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedServerHeader
+ * The header name for forwarded server (default x-forwarded-server)
+ */
+ public void setForwardedServerHeader(String forwardedServerHeader)
+ {
+ _forwardedServerHeader = forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the forwarded for header
+ */
+ public String getForwardedForHeader()
+ {
+ return _forwardedForHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedRemoteAddressHeader
+ * The header name for forwarded for (default x-forwarded-for)
+ */
+ public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+ {
+ _forwardedForHeader = forwardedRemoteAddressHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the forwardedProtoHeader.
+ *
+ * @return the forwardedProtoHeader (default X-Forwarded-For)
+ */
+ public String getForwardedProtoHeader()
+ {
+ return _forwardedProtoHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the forwardedProtoHeader.
+ *
+ * @param forwardedProtoHeader
+ * the forwardedProtoHeader to set (default X-Forwarded-For)
+ */
+ public void setForwardedProtoHeader(String forwardedProtoHeader)
+ {
+ _forwardedProtoHeader = forwardedProtoHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The header name holding a forwarded cipher suite (default null)
+ */
+ public String getForwardedCipherSuiteHeader()
+ {
+ return _forwardedCipherSuiteHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedCipherSuite
+ * The header name holding a forwarded cipher suite (default null)
+ */
+ public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+ {
+ _forwardedCipherSuiteHeader = forwardedCipherSuite;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The header name holding a forwarded SSL Session ID (default null)
+ */
+ public String getForwardedSslSessionIdHeader()
+ {
+ return _forwardedSslSessionIdHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedSslSessionId
+ * The header name holding a forwarded SSL Session ID (default null)
+ */
+ public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+ {
+ _forwardedSslSessionIdHeader = forwardedSslSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void customize(Connector connector, HttpConfiguration config, Request request)
+ {
+ HttpFields httpFields = request.getHttpFields();
+
+ // Do SSL first
+ if (getForwardedCipherSuiteHeader()!=null)
+ {
+ String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+ if (cipher_suite!=null)
+ request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+ }
+ if (getForwardedSslSessionIdHeader()!=null)
+ {
+ String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+ if(ssl_session_id!=null)
+ {
+ request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+ request.setScheme(HttpScheme.HTTPS.asString());
+ }
+ }
+
+ // Retrieving headers from the request
+ String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+ String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+ String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+ String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+ if (_hostHeader != null)
+ {
+ // Update host header
+ httpFields.put(HttpHeader.HOST.toString(),_hostHeader);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+ else if (forwardedHost != null)
+ {
+ // Update host header
+ httpFields.put(HttpHeader.HOST.toString(),forwardedHost);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+ else if (forwardedServer != null)
+ {
+ // Use provided server name
+ request.setServerName(forwardedServer);
+ }
+
+ if (forwardedFor != null)
+ {
+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
+ }
+
+ if (forwardedProto != null)
+ {
+ request.setScheme(forwardedProto);
+ if (forwardedProto.equals(config.getSecureScheme()))
+ request.setSecure(true);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected String getLeftMostFieldValue(HttpFields fields, String header)
+ {
+ if (header == null)
+ return null;
+
+ String headerValue = fields.getStringField(header);
+
+ if (headerValue == null)
+ return null;
+
+ int commaIndex = headerValue.indexOf(',');
+
+ if (commaIndex == -1)
+ {
+ // Single value
+ return headerValue;
+ }
+
+ // The left-most value is the farthest downstream client
+ return headerValue.substring(0,commaIndex);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Handler.java b/lib/jetty/org/eclipse/jetty/server/Handler.java
new file mode 100644
index 00000000..cfe7b9a2
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Handler.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A Jetty Server Handler.
+ *
+ * A Handler instance is required by a {@link Server} to handle incoming
+ * HTTP requests. A Handler may:
+ * Completely generate the HTTP Response
+ * Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
+ * Pass the request to one or more other Handlers (see {@link HandlerCollection}).
+ *
+ *
+ * Handlers are passed the servlet API request and response object, but are
+ * not Servlets. The servlet container is implemented by handlers for
+ * context, security, session and servlet that modify the request object
+ * before passing it to the next stage of handling.
+ *
+ */
+@ManagedObject("Jetty Handler")
+public interface Handler extends LifeCycle, Destroyable
+{
+ /* ------------------------------------------------------------ */
+ /** Handle a request.
+ * @param target The target of the request - either a URI or a name.
+ * @param baseRequest The original unwrapped request object.
+ * @param request The request either as the {@link Request}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+ * method can be used access the Request object if required.
+ * @param response The response as the {@link Response}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+ * method can be used access the Response object if required.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException;
+
+ public void setServer(Server server);
+
+ @ManagedAttribute(value="the jetty server for this handler", readonly=true)
+ public Server getServer();
+
+ @ManagedOperation(value="destroy associated resources", impact="ACTION")
+ public void destroy();
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java b/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java
new file mode 100644
index 00000000..51fd4791
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ *
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}.
+ *
+ */
+@ManagedObject("Handler of Multiple Handlers")
+public interface HandlerContainer extends LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return array of handlers directly contained by this handler.
+ */
+ @ManagedAttribute("handlers in this container")
+ public Handler[] getHandlers();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return array of all handlers contained by this handler and it's children
+ */
+ @ManagedAttribute("all contained handlers")
+ public Handler[] getChildHandlers();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param byclass
+ * @return array of all handlers contained by this handler and it's children of the passed type.
+ */
+ public Handler[] getChildHandlersByClass(Class> byclass);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param byclass
+ * @return first handler of all handlers contained by this handler and it's children of the passed type.
+ */
+ public T getChildHandlerByClass(Class byclass);
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java b/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java
new file mode 100644
index 00000000..b39b25dc
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
+ *
+ * In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
+ * a redirect, and the {@code Location} header is usually constructed from the {@code Host}
+ * header; if the {@code Host} header is missing, the server may query the connector for its
+ * IP address in order to construct the {@code Location} header, and thus leak to clients
+ * internal IP addresses.
+ *
+ * This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
+ * optionally a {@code serverPort}.
+ * If the {@code Host} header is absent, the configured {@code serverName} will be set on
+ * the request so that {@link HttpServletRequest#getServerName()} will return that value,
+ * and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
+ */
+public class HostHeaderCustomizer implements HttpConfiguration.Customizer
+{
+ private final String serverName;
+ private final int serverPort;
+
+ /**
+ * @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
+ */
+ public HostHeaderCustomizer(String serverName)
+ {
+ this(serverName, 0);
+ }
+
+ /**
+ * @param serverName the {@code serverName} to set on the request
+ * @param serverPort the {@code serverPort} to set on the request
+ */
+ public HostHeaderCustomizer(String serverName, int serverPort)
+ {
+ this.serverName = Objects.requireNonNull(serverName);
+ this.serverPort = serverPort;
+ }
+
+ @Override
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+ {
+ if (request.getHeader("Host") == null)
+ {
+ request.setServerName(serverName);
+ if (serverPort > 0)
+ request.setServerPort(serverPort);
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannel.java b/lib/jetty/org/eclipse/jetty/server/HttpChannel.java
new file mode 100644
index 00000000..07b09ab7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpChannel.java
@@ -0,0 +1,860 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** HttpChannel.
+ * Represents a single endpoint for HTTP semantic processing.
+ * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
+ * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
+ * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
+ * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
+ * HttpParser.RequestHandler callbacks. The completion of the active phase is signalled by a call to
+ * HttpTransport.completed().
+ *
+ */
+public class HttpChannel implements HttpParser.RequestHandler, Runnable, HttpParser.ProxyHandler
+{
+ private static final Logger LOG = Log.getLogger(HttpChannel.class);
+ private static final ThreadLocal> __currentChannel = new ThreadLocal<>();
+
+ /* ------------------------------------------------------------ */
+ /** Get the current channel that this thread is dispatched to.
+ * @see Request#getAttribute(String) for a more general way to access the HttpChannel
+ * @return the current HttpChannel or null
+ */
+ public static HttpChannel> getCurrentHttpChannel()
+ {
+ return __currentChannel.get();
+ }
+
+ protected static HttpChannel> setCurrentHttpChannel(HttpChannel> channel)
+ {
+ HttpChannel> last=__currentChannel.get();
+ if (channel==null)
+ __currentChannel.remove();
+ else
+ __currentChannel.set(channel);
+ return last;
+ }
+
+ private final AtomicBoolean _committed = new AtomicBoolean();
+ private final AtomicInteger _requests = new AtomicInteger();
+ private final Connector _connector;
+ private final HttpConfiguration _configuration;
+ private final EndPoint _endPoint;
+ private final HttpTransport _transport;
+ private final HttpURI _uri;
+ private final HttpChannelState _state;
+ private final Request _request;
+ private final Response _response;
+ private HttpVersion _version = HttpVersion.HTTP_1_1;
+ private boolean _expect = false;
+ private boolean _expect100Continue = false;
+ private boolean _expect102Processing = false;
+
+ public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input)
+ {
+ _connector = connector;
+ _configuration = configuration;
+ _endPoint = endPoint;
+ _transport = transport;
+
+ _uri = new HttpURI(URIUtil.__CHARSET);
+ _state = new HttpChannelState(this);
+ input.init(_state);
+ _request = new Request(this, input);
+ _response = new Response(this, new HttpOutput(this));
+ }
+
+ public HttpChannelState getState()
+ {
+ return _state;
+ }
+
+ public HttpVersion getHttpVersion()
+ {
+ return _version;
+ }
+ /**
+ * @return the number of requests handled by this connection
+ */
+ public int getRequests()
+ {
+ return _requests.get();
+ }
+
+ public Connector getConnector()
+ {
+ return _connector;
+ }
+
+ public HttpTransport getHttpTransport()
+ {
+ return _transport;
+ }
+
+ public ByteBufferPool getByteBufferPool()
+ {
+ return _connector.getByteBufferPool();
+ }
+
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _configuration;
+ }
+
+ public Server getServer()
+ {
+ return _connector.getServer();
+ }
+
+ public Request getRequest()
+ {
+ return _request;
+ }
+
+ public Response getResponse()
+ {
+ return _response;
+ }
+
+ public EndPoint getEndPoint()
+ {
+ return _endPoint;
+ }
+
+ public InetSocketAddress getLocalAddress()
+ {
+ return _endPoint.getLocalAddress();
+ }
+
+ public InetSocketAddress getRemoteAddress()
+ {
+ return _endPoint.getRemoteAddress();
+ }
+
+ @Override
+ public int getHeaderCacheSize()
+ {
+ return _configuration.getHeaderCacheSize();
+ }
+
+ /**
+ * If the associated response has the Expect header set to 100 Continue,
+ * then accessing the input stream indicates that the handler/servlet
+ * is ready for the request body and thus a 100 Continue response is sent.
+ *
+ * @throws IOException if the InputStream cannot be created
+ */
+ public void continue100(int available) throws IOException
+ {
+ // If the client is expecting 100 CONTINUE, then send it now.
+ // TODO: consider using an AtomicBoolean ?
+ if (isExpecting100Continue())
+ {
+ _expect100Continue = false;
+
+ // is content missing?
+ if (available == 0)
+ {
+ if (_response.isCommitted())
+ throw new IOException("Committed before 100 Continues");
+
+ // TODO: break this dependency with HttpGenerator
+ boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
+ if (!committed)
+ throw new IOException("Concurrent commit while trying to send 100-Continue");
+ }
+ }
+ }
+
+ public void reset()
+ {
+ _committed.set(false);
+ _expect = false;
+ _expect100Continue = false;
+ _expect102Processing = false;
+ _request.recycle();
+ _response.recycle();
+ _uri.clear();
+ }
+
+ @Override
+ public void run()
+ {
+ handle();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the channel is ready to continue handling (ie it is not suspended)
+ */
+ public boolean handle()
+ {
+ LOG.debug("{} handle enter", this);
+
+ final HttpChannel>last = setCurrentHttpChannel(this);
+
+ String threadName = null;
+ if (LOG.isDebugEnabled())
+ {
+ threadName = Thread.currentThread().getName();
+ Thread.currentThread().setName(threadName + " - " + _uri);
+ }
+
+ HttpChannelState.Action action = _state.handling();
+ try
+ {
+ // Loop here to handle async request redispatches.
+ // The loop is controlled by the call to async.unhandle in the
+ // finally block below. Unhandle will return false only if an async dispatch has
+ // already happened when unhandle is called.
+ loop: while (action.ordinal()Sends an error 500, performing a special logic to detect whether the request is suspended,
+ * to avoid concurrent writes from the application.
+ * It may happen that the application suspends, and then throws an exception, while an application
+ * spawned thread writes the response content; in such case, we attempt to commit the error directly
+ * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.
+ *
+ * @param x the Throwable that caused the problem
+ */
+ protected void handleException(Throwable x)
+ {
+ try
+ {
+ _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
+ _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
+ if (_state.isSuspended())
+ {
+ HttpFields fields = new HttpFields();
+ fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+ ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
+ boolean committed = sendResponse(info, null, true);
+ if (!committed)
+ LOG.warn("Could not send response error 500: "+x);
+ _request.getAsyncContext().complete();
+ }
+ else if (isCommitted())
+ {
+ _transport.abort();
+ if (!(x instanceof EofException))
+ LOG.warn("Could not send response error 500: "+x);
+ }
+ else
+ {
+ _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
+ _response.sendError(500, x.getMessage());
+ }
+ }
+ catch (IOException e)
+ {
+ // We tried our best, just log
+ LOG.debug("Could not commit response error 500", e);
+ }
+ }
+
+ public boolean isExpecting100Continue()
+ {
+ return _expect100Continue;
+ }
+
+ public boolean isExpecting102Processing()
+ {
+ return _expect102Processing;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{r=%s,a=%s,uri=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _requests,
+ _state.getState(),
+ _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+ );
+ }
+
+ @Override
+ public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+ {
+ _request.setAttribute("PROXY", protocol);
+ _request.setServerName(sAddr);
+ _request.setServerPort(dPort);
+ _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
+ }
+
+ @Override
+ public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+ {
+ _expect = false;
+ _expect100Continue = false;
+ _expect102Processing = false;
+
+ _request.setTimeStamp(System.currentTimeMillis());
+ _request.setMethod(httpMethod, method);
+
+ if (httpMethod == HttpMethod.CONNECT)
+ _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+ else
+ _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+ _request.setUri(_uri);
+
+ String path;
+ try
+ {
+ path = _uri.getDecodedPath();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+ LOG.ignore(e);
+ path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
+ }
+
+ String info = URIUtil.canonicalPath(path);
+
+ if (info == null)
+ {
+ if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
+ {
+ info = "/";
+ _request.setRequestURI("");
+ }
+ else
+ {
+ badMessage(400,null);
+ return true;
+ }
+ }
+ _request.setPathInfo(info);
+ _version = version == null ? HttpVersion.HTTP_0_9 : version;
+ _request.setHttpVersion(_version);
+
+ return false;
+ }
+
+ @Override
+ public boolean parsedHeader(HttpField field)
+ {
+ HttpHeader header=field.getHeader();
+ String value=field.getValue();
+ if (value == null)
+ value = "";
+ if (header != null)
+ {
+ switch (header)
+ {
+ case EXPECT:
+ if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+ {
+ HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
+ switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
+ {
+ case CONTINUE:
+ _expect100Continue = true;
+ break;
+
+ case PROCESSING:
+ _expect102Processing = true;
+ break;
+
+ default:
+ String[] values = value.split(",");
+ for (int i = 0; values != null && i < values.length; i++)
+ {
+ expect = HttpHeaderValue.CACHE.get(values[i].trim());
+ if (expect == null)
+ _expect = true;
+ else
+ {
+ switch (expect)
+ {
+ case CONTINUE:
+ _expect100Continue = true;
+ break;
+ case PROCESSING:
+ _expect102Processing = true;
+ break;
+ default:
+ _expect = true;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case CONTENT_TYPE:
+ MimeTypes.Type mime = MimeTypes.CACHE.get(value);
+ String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
+ if (charset != null)
+ _request.setCharacterEncodingUnchecked(charset);
+ break;
+ default:
+ }
+ }
+
+ if (field.getName()!=null)
+ _request.getHttpFields().add(field);
+ return false;
+ }
+
+ @Override
+ public boolean parsedHostHeader(String host, int port)
+ {
+ if (_uri.getHost()==null)
+ {
+ _request.setServerName(host);
+ _request.setServerPort(port);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ _requests.incrementAndGet();
+ switch (_version)
+ {
+ case HTTP_0_9:
+ break;
+
+ case HTTP_1_0:
+ if (_configuration.getSendDateHeader())
+ _response.getHttpFields().put(_connector.getServer().getDateField());
+ break;
+
+ case HTTP_1_1:
+ if (_configuration.getSendDateHeader())
+ _response.getHttpFields().put(_connector.getServer().getDateField());
+
+ if (_expect)
+ {
+ badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
+ return true;
+ }
+
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean content(T item)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} content {}", this, item);
+ @SuppressWarnings("unchecked")
+ HttpInput input = (HttpInput)_request.getHttpInput();
+ input.content(item);
+
+ return false;
+ }
+
+ @Override
+ public boolean messageComplete()
+ {
+ LOG.debug("{} messageComplete", this);
+ _request.getHttpInput().messageComplete();
+ return true;
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ _request.getHttpInput().earlyEOF();
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ if (status < 400 || status > 599)
+ status = HttpStatus.BAD_REQUEST_400;
+
+ try
+ {
+ if (_state.handling()==Action.REQUEST_DISPATCH)
+ {
+ ByteBuffer content=null;
+ HttpFields fields=new HttpFields();
+
+ ErrorHandler handler=getServer().getBean(ErrorHandler.class);
+ if (handler!=null)
+ content=handler.badMessageError(status,reason,fields);
+
+ sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ if (_state.unhandle()==Action.COMPLETE)
+ _state.completed();
+ else
+ throw new IllegalStateException();
+ }
+ }
+
+ protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
+ {
+ // TODO check that complete only set true once by changing _committed to AtomicRef
+ boolean committing = _committed.compareAndSet(false, true);
+ if (committing)
+ {
+ // We need an info to commit
+ if (info==null)
+ info = _response.newResponseInfo();
+
+ // wrap callback to process 100 or 500 responses
+ final int status=info.getStatus();
+ final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
+
+ // committing write
+ _transport.send(info, content, complete, committed);
+ }
+ else if (info==null)
+ {
+ // This is a normal write
+ _transport.send(content, complete, callback);
+ }
+ else
+ {
+ callback.failed(new IllegalStateException("committed"));
+ }
+ return committing;
+ }
+
+ protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
+ {
+ try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
+ {
+ boolean committing = sendResponse(info,content,complete,blocker);
+ blocker.block();
+ return committing;
+ }
+ }
+
+ public boolean isCommitted()
+ {
+ return _committed.get();
+ }
+
+ /**
+ * Non-Blocking write, committing the response if needed.
+ *
+ * @param content the content buffer to write
+ * @param complete whether the content is complete for the response
+ * @param callback Callback when complete or failed
+ */
+ protected void write(ByteBuffer content, boolean complete, Callback callback)
+ {
+ sendResponse(null,content,complete,callback);
+ }
+
+ protected void execute(Runnable task)
+ {
+ _connector.getExecutor().execute(task);
+ }
+
+ public Scheduler getScheduler()
+ {
+ return _connector.getScheduler();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+ */
+ public boolean useDirectBuffers()
+ {
+ return getEndPoint() instanceof ChannelEndPoint;
+ }
+
+ /**
+ * If a write or similar to this channel fails this method should be called. The standard implementation
+ * of {@link #failed()} is a noop. But the different implementations of HttpChannel might want to take actions.
+ */
+ public void failed()
+ {
+ }
+
+ private class CommitCallback implements Callback
+ {
+ private final Callback _callback;
+
+ private CommitCallback(Callback callback)
+ {
+ _callback = callback;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ if (x instanceof EofException || x instanceof ClosedChannelException)
+ {
+ LOG.debug(x);
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+ else
+ {
+ LOG.warn("Commit failed",x);
+ _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+
+ @Override
+ public void failed(Throwable th)
+ {
+ LOG.ignore(th);
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+ });
+ }
+ }
+ }
+
+ private class Commit100Callback extends CommitCallback
+ {
+ private Commit100Callback(Callback callback)
+ {
+ super(callback);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _committed.set(false);
+ super.succeeded();
+ }
+
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java b/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java
new file mode 100644
index 00000000..7ff4d5a2
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java
@@ -0,0 +1,714 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Implementation of AsyncContext interface that holds the state of request-response cycle.
+ */
+public class HttpChannelState
+{
+ private static final Logger LOG = Log.getLogger(HttpChannelState.class);
+
+ private final static long DEFAULT_TIMEOUT=30000L;
+
+ /** The dispatched state of the HttpChannel, used to control the overall livecycle
+ */
+ public enum State
+ {
+ IDLE, // Idle request
+ DISPATCHED, // Request dispatched to filter/servlet
+ ASYNC_WAIT, // Suspended and parked
+ ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT
+ ASYNC_IO, // Has been dispatched for async IO
+ COMPLETING, // Request is completable
+ COMPLETED // Request is complete
+ }
+
+ /**
+ * The actions to take as the channel moves from state to state.
+ */
+ public enum Action
+ {
+ REQUEST_DISPATCH, // handle a normal request dispatch
+ ASYNC_DISPATCH, // handle an async request dispatch
+ ASYNC_EXPIRED, // handle an async timeout
+ WRITE_CALLBACK, // handle an IO write callback
+ READ_CALLBACK, // handle an IO read callback
+ WAIT, // Wait for further events
+ COMPLETE // Complete the channel
+ }
+
+ /**
+ * The state of the servlet async API. This can lead or follow the
+ * channel dispatch state and also includes reasons such as expired,
+ * dispatched or completed.
+ */
+ public enum Async
+ {
+ STARTED,
+ DISPATCH,
+ COMPLETE,
+ EXPIRING,
+ EXPIRED
+ }
+
+ private final boolean DEBUG=LOG.isDebugEnabled();
+ private final HttpChannel> _channel;
+
+ private List _asyncListeners;
+ private State _state;
+ private Async _async;
+ private boolean _initial;
+ private boolean _asyncRead;
+ private boolean _asyncWrite;
+ private long _timeoutMs=DEFAULT_TIMEOUT;
+ private AsyncContextEvent _event;
+
+ protected HttpChannelState(HttpChannel> channel)
+ {
+ _channel=channel;
+ _state=State.IDLE;
+ _async=null;
+ _initial=true;
+ }
+
+ public State getState()
+ {
+ synchronized(this)
+ {
+ return _state;
+ }
+ }
+
+ public void addListener(AsyncListener listener)
+ {
+ synchronized(this)
+ {
+ if (_asyncListeners==null)
+ _asyncListeners=new ArrayList<>();
+ _asyncListeners.add(listener);
+ }
+ }
+
+ public void setTimeout(long ms)
+ {
+ synchronized(this)
+ {
+ _timeoutMs=ms;
+ }
+ }
+
+ public long getTimeout()
+ {
+ synchronized(this)
+ {
+ return _timeoutMs;
+ }
+ }
+
+ public AsyncContextEvent getAsyncContextEvent()
+ {
+ synchronized(this)
+ {
+ return _event;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ synchronized (this)
+ {
+ return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
+ }
+ }
+
+ public String getStatusString()
+ {
+ synchronized (this)
+ {
+ return String.format("s=%s i=%b a=%s",_state,_initial,_async);
+ }
+ }
+
+ /**
+ * @return Next handling of the request should proceed
+ */
+ protected Action handling()
+ {
+ synchronized (this)
+ {
+ if(DEBUG)
+ LOG.debug("{} handling {}",this,_state);
+ switch(_state)
+ {
+ case IDLE:
+ _initial=true;
+ _state=State.DISPATCHED;
+ return Action.REQUEST_DISPATCH;
+
+ case COMPLETING:
+ return Action.COMPLETE;
+
+ case COMPLETED:
+ return Action.WAIT;
+
+ case ASYNC_WOKEN:
+ if (_asyncRead)
+ {
+ _state=State.ASYNC_IO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+ if (_asyncWrite)
+ {
+ _state=State.ASYNC_IO;
+ _asyncWrite=false;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ Async async=_async;
+ switch(async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRING:
+ break;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case STARTED:
+ // TODO
+ if (DEBUG)
+ LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+ .getStatusString()));
+ return Action.WAIT;
+ }
+ }
+
+ return Action.WAIT;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+ }
+
+ public void startAsync(AsyncContextEvent event)
+ {
+ final List lastAsyncListeners;
+
+ synchronized (this)
+ {
+ if (_state!=State.DISPATCHED || _async!=null)
+ throw new IllegalStateException(this.getStatusString());
+
+ _async=Async.STARTED;
+ _event=event;
+ lastAsyncListeners=_asyncListeners;
+ _asyncListeners=null;
+ }
+
+ if (lastAsyncListeners!=null)
+ {
+ for (AsyncListener listener : lastAsyncListeners)
+ {
+ try
+ {
+ listener.onStartAsync(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+
+ protected void error(Throwable th)
+ {
+ synchronized (this)
+ {
+ if (_event!=null)
+ _event.setThrowable(th);
+ }
+ }
+
+ /**
+ * Signal that the HttpConnection has finished handling the request.
+ * For blocking connectors, this call may block if the request has
+ * been suspended (startAsync called).
+ * @return next actions
+ * be handled again (eg because of a resume that happened before unhandle was called)
+ */
+ protected Action unhandle()
+ {
+ synchronized (this)
+ {
+ if(DEBUG)
+ LOG.debug("{} unhandle {}",this,_state);
+
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ break;
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+
+ if (_asyncRead)
+ {
+ _state=State.ASYNC_IO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+
+ if (_asyncWrite)
+ {
+ _asyncWrite=false;
+ _state=State.ASYNC_IO;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ _initial=false;
+ switch(_async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ _async=null;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case EXPIRING:
+ case STARTED:
+ scheduleTimeout();
+ _state=State.ASYNC_WAIT;
+ return Action.WAIT;
+ }
+ }
+
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
+ }
+ }
+
+ public void dispatch(ServletContext context, String path)
+ {
+ boolean dispatch;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+ _async=Async.DISPATCH;
+
+ if (context!=null)
+ _event.setDispatchContext(context);
+ if (path!=null)
+ _event.setDispatchPath(path);
+
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ dispatch=false;
+ break;
+ case ASYNC_WAIT:
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ break;
+ case ASYNC_WOKEN:
+ dispatch=false;
+ break;
+ default:
+ LOG.warn("async dispatched when complete {}",this);
+ dispatch=false;
+ break;
+ }
+ }
+
+ cancelTimeout();
+ if (dispatch)
+ scheduleDispatch();
+ }
+
+ protected void expired()
+ {
+ final List aListeners;
+ AsyncContextEvent event;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED)
+ return;
+ _async=Async.EXPIRING;
+ event=_event;
+ aListeners=_asyncListeners;
+ }
+
+ if (aListeners!=null)
+ {
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ listener.onTimeout(event);
+ }
+ catch(Exception e)
+ {
+ LOG.debug(e);
+ event.setThrowable(e);
+ _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+ break;
+ }
+ }
+ }
+
+ boolean dispatch=false;
+ synchronized (this)
+ {
+ if (_async==Async.EXPIRING)
+ {
+ _async=Async.EXPIRED;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ }
+ }
+ }
+
+ if (dispatch)
+ scheduleDispatch();
+ }
+
+ public void complete()
+ {
+ // just like resume, except don't set _dispatched=true;
+ boolean handle=false;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException(this.getStatusString());
+ _async=Async.COMPLETE;
+ if (_state==State.ASYNC_WAIT)
+ {
+ handle=true;
+ _state=State.ASYNC_WOKEN;
+ }
+ }
+
+ cancelTimeout();
+ if (handle)
+ {
+ ContextHandler handler=getContextHandler();
+ if (handler!=null)
+ handler.handle(_channel);
+ else
+ _channel.handle();
+ }
+ }
+
+ public void errorComplete()
+ {
+ synchronized (this)
+ {
+ _async=Async.COMPLETE;
+ _event.setDispatchContext(null);
+ _event.setDispatchPath(null);
+ }
+
+ cancelTimeout();
+ }
+
+ protected void completed()
+ {
+ final List aListeners;
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case COMPLETING:
+ _state=State.COMPLETED;
+ aListeners=_asyncListeners;
+ event=_event;
+ break;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+
+ if (event!=null)
+ {
+ if (aListeners!=null)
+ {
+ if (event.getThrowable()!=null)
+ {
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+ }
+
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ if (event.getThrowable()!=null)
+ listener.onError(event);
+ else
+ listener.onComplete(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+ event.completed();
+ }
+ }
+
+ protected void recycle()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ throw new IllegalStateException(getStatusString());
+ default:
+ break;
+ }
+ _asyncListeners=null;
+ _state=State.IDLE;
+ _async=null;
+ _initial=true;
+ _asyncRead=false;
+ _asyncWrite=false;
+ _timeoutMs=DEFAULT_TIMEOUT;
+ cancelTimeout();
+ _event=null;
+ }
+ }
+
+ protected void scheduleDispatch()
+ {
+ _channel.execute(_channel);
+ }
+
+ protected void scheduleTimeout()
+ {
+ Scheduler scheduler = _channel.getScheduler();
+ if (scheduler!=null && _timeoutMs>0)
+ _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
+ }
+
+ protected void cancelTimeout()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+ if (event!=null)
+ event.cancelTimeoutTask();
+ }
+
+ public boolean isExpired()
+ {
+ synchronized (this)
+ {
+ return _async==Async.EXPIRED;
+ }
+ }
+
+ public boolean isInitial()
+ {
+ synchronized(this)
+ {
+ return _initial;
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ synchronized(this)
+ {
+ return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
+ }
+ }
+
+ boolean isCompleting()
+ {
+ synchronized (this)
+ {
+ return _state==State.COMPLETING;
+ }
+ }
+
+ boolean isCompleted()
+ {
+ synchronized (this)
+ {
+ return _state == State.COMPLETED;
+ }
+ }
+
+ public boolean isAsyncStarted()
+ {
+ synchronized (this)
+ {
+ if (_state==State.DISPATCHED)
+ return _async!=null;
+ return _async==Async.STARTED || _async==Async.EXPIRING;
+ }
+ }
+
+ public boolean isAsync()
+ {
+ synchronized (this)
+ {
+ return !_initial || _async!=null;
+ }
+ }
+
+ public Request getBaseRequest()
+ {
+ return _channel.getRequest();
+ }
+
+ public HttpChannel> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public ContextHandler getContextHandler()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+
+ if (event!=null)
+ {
+ Context context=((Context)event.getServletContext());
+ if (context!=null)
+ return context.getContextHandler();
+ }
+ return null;
+ }
+
+ public ServletResponse getServletResponse()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+ if (event!=null && event.getSuppliedResponse()!=null)
+ return event.getSuppliedResponse();
+ return _channel.getResponse();
+ }
+
+ public Object getAttribute(String name)
+ {
+ return _channel.getRequest().getAttribute(name);
+ }
+
+ public void removeAttribute(String name)
+ {
+ _channel.getRequest().removeAttribute(name);
+ }
+
+ public void setAttribute(String name, Object attribute)
+ {
+ _channel.getRequest().setAttribute(name,attribute);
+ }
+
+ public void onReadPossible()
+ {
+ boolean handle=false;
+
+ synchronized (this)
+ {
+ _asyncRead=true;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ handle=true;
+ }
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
+ public void onWritePossible()
+ {
+ boolean handle=false;
+
+ synchronized (this)
+ {
+ _asyncWrite=true;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ handle=true;
+ }
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java b/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java
new file mode 100644
index 00000000..352af25c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java
@@ -0,0 +1,269 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+
+/* ------------------------------------------------------------ */
+/** HTTP Configuration.
+ * This class is a holder of HTTP configuration for use by the
+ * {@link HttpChannel} class. Typically a HTTPConfiguration instance
+ * is instantiated and passed to a {@link ConnectionFactory} that can
+ * create HTTP channels (eg HTTP, AJP or SPDY).
+ * The configuration held by this class is not for the wire protocol,
+ * but for the interpretation and handling of HTTP requests that could
+ * be transported by a variety of protocols.
+ *
+ */
+@ManagedObject("HTTP Configuration")
+public class HttpConfiguration
+{
+ public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
+ private List _customizers=new CopyOnWriteArrayList<>();
+ private int _outputBufferSize=32*1024;
+ private int _requestHeaderSize=8*1024;
+ private int _responseHeaderSize=8*1024;
+ private int _headerCacheSize=512;
+ private int _securePort;
+ private String _secureScheme = HttpScheme.HTTPS.asString();
+ private boolean _sendServerVersion = true; //send Server: header
+ private boolean _sendXPoweredBy = false; //send X-Powered-By: header
+ private boolean _sendDateHeader = true; //send Date: header
+
+ public interface Customizer
+ {
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request);
+ }
+
+ public interface ConnectionFactory
+ {
+ HttpConfiguration getHttpConfiguration();
+ }
+
+ public HttpConfiguration()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Create a configuration from another.
+ * @param config The configuration to copy.
+ */
+ public HttpConfiguration(HttpConfiguration config)
+ {
+ _customizers.addAll(config._customizers);
+ _outputBufferSize=config._outputBufferSize;
+ _requestHeaderSize=config._requestHeaderSize;
+ _responseHeaderSize=config._responseHeaderSize;
+ _securePort=config._securePort;
+ _secureScheme=config._secureScheme;
+ _sendDateHeader=config._sendDateHeader;
+ _sendServerVersion=config._sendServerVersion;
+ _headerCacheSize=config._headerCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a {@link Customizer} that is invoked for every
+ * request received.
+ * Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
+ * optional protocol semantics (eg {@link SecureRequestCustomizer}).
+ * @param customizer A request customizer
+ */
+ public void addCustomizer(Customizer customizer)
+ {
+ _customizers.add(customizer);
+ }
+
+ /* ------------------------------------------------------------ */
+ public List getCustomizers()
+ {
+ return _customizers;
+ }
+
+ public T getCustomizer(Class type)
+ {
+ for (Customizer c : _customizers)
+ if (type.isAssignableFrom(c.getClass()))
+ return (T)c;
+ return null;
+ }
+
+ @ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
+ public int getOutputBufferSize()
+ {
+ return _outputBufferSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
+ public int getRequestHeaderSize()
+ {
+ return _requestHeaderSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
+ public int getResponseHeaderSize()
+ {
+ return _responseHeaderSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
+ public int getHeaderCacheSize()
+ {
+ return _headerCacheSize;
+ }
+
+ @ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
+ public int getSecurePort()
+ {
+ return _securePort;
+ }
+
+ @ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
+ public String getSecureScheme()
+ {
+ return _secureScheme;
+ }
+
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ _sendServerVersion = sendServerVersion;
+ }
+
+ @ManagedAttribute("if true, send the Server header in responses")
+ public boolean getSendServerVersion()
+ {
+ return _sendServerVersion;
+ }
+
+ public void setSendXPoweredBy (boolean sendXPoweredBy)
+ {
+ _sendXPoweredBy=sendXPoweredBy;
+ }
+
+ @ManagedAttribute("if true, send the X-Powered-By header in responses")
+ public boolean getSendXPoweredBy()
+ {
+ return _sendXPoweredBy;
+ }
+
+ public void setSendDateHeader(boolean sendDateHeader)
+ {
+ _sendDateHeader = sendDateHeader;
+ }
+
+ @ManagedAttribute("if true, include the date in HTTP headers")
+ public boolean getSendDateHeader()
+ {
+ return _sendDateHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the {@link Customizer}s that are invoked for every
+ * request received.
+ * Customisers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
+ * optional protocol semantics (eg {@link SecureRequestCustomizer}).
+ * @param customizers
+ */
+ public void setCustomizers(List customizers)
+ {
+ _customizers.clear();
+ _customizers.addAll(customizers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the size of the buffer into which response content is aggregated
+ * before being sent to the client. A larger buffer can improve performance by allowing
+ * a content producer to run without blocking, however larger buffers consume more memory and
+ * may induce some latency before a client starts processing the content.
+ * @param responseBufferSize buffer size in bytes.
+ */
+ public void setOutputBufferSize(int responseBufferSize)
+ {
+ _outputBufferSize = responseBufferSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum size of a request header.
+ * Larger headers will allow for more and/or larger cookies plus larger form content encoded
+ * in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
+ * attacks.
+ * @param requestHeaderSize Max header size in bytes
+ */
+ public void setRequestHeaderSize(int requestHeaderSize)
+ {
+ _requestHeaderSize = requestHeaderSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum size of a response header.
+ *
+ * Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection).
+ * However, larger headers will also consume more memory.
+ * @param responseHeaderSize Response header size in bytes.
+ */
+ public void setResponseHeaderSize(int responseHeaderSize)
+ {
+ _responseHeaderSize = responseHeaderSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the header field cache size.
+ * @param headerCacheSize The size in bytes of the header field cache.
+ */
+ public void setHeaderCacheSize(int headerCacheSize)
+ {
+ _headerCacheSize = headerCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL
+ * redirections.
+ * @param confidentialPort
+ */
+ public void setSecurePort(int confidentialPort)
+ {
+ _securePort = confidentialPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the URI scheme used for CONFIDENTIAL and INTEGRAL
+ * redirections.
+ * @param confidentialScheme A string like"https"
+ */
+ public void setSecureScheme(String confidentialScheme)
+ {
+ _secureScheme = confidentialScheme;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%d,%d/%d,%s://:%d,%s}",this.getClass().getSimpleName(),hashCode(),_outputBufferSize,_requestHeaderSize,_responseHeaderSize,_secureScheme,_securePort,_customizers);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnection.java b/lib/jetty/org/eclipse/jetty/server/HttpConnection.java
new file mode 100644
index 00000000..16cbbf93
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpConnection.java
@@ -0,0 +1,790 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A {@link Connection} that handles the HTTP protocol.
+ */
+public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport
+{
+ public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
+ private static final boolean REQUEST_BUFFER_DIRECT=false;
+ private static final boolean HEADER_BUFFER_DIRECT=false;
+ private static final boolean CHUNK_BUFFER_DIRECT=false;
+ private static final Logger LOG = Log.getLogger(HttpConnection.class);
+ private static final ThreadLocal __currentConnection = new ThreadLocal<>();
+
+ private final HttpConfiguration _config;
+ private final Connector _connector;
+ private final ByteBufferPool _bufferPool;
+ private final HttpGenerator _generator;
+ private final HttpChannelOverHttp _channel;
+ private final HttpParser _parser;
+ private volatile ByteBuffer _requestBuffer = null;
+ private volatile ByteBuffer _chunk = null;
+
+
+ /* ------------------------------------------------------------ */
+ /** Get the current connection that this thread is dispatched to.
+ * Note that a thread may be processing a request asynchronously and
+ * thus not be dispatched to the connection.
+ * @see Request#getAttribute(String) for a more general way to access the HttpConnection
+ * @return the current HttpConnection or null
+ */
+ public static HttpConnection getCurrentConnection()
+ {
+ return __currentConnection.get();
+ }
+
+ protected static HttpConnection setCurrentConnection(HttpConnection connection)
+ {
+ HttpConnection last=__currentConnection.get();
+ if (connection==null)
+ __currentConnection.remove();
+ else
+ __currentConnection.set(connection);
+ return last;
+ }
+
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _config;
+ }
+
+ public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+ {
+ // Tell AbstractConnector executeOnFillable==true because we want the same thread that
+ // does the HTTP parsing to handle the request so its cache is hot
+ super(endPoint, connector.getExecutor(),true);
+
+ _config = config;
+ _connector = connector;
+ _bufferPool = _connector.getByteBufferPool();
+ _generator = newHttpGenerator();
+ HttpInput input = newHttpInput();
+ _channel = newHttpChannel(input);
+ _parser = newHttpParser();
+ LOG.debug("New HTTP Connection {}", this);
+ }
+
+ protected HttpGenerator newHttpGenerator()
+ {
+ return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
+ }
+
+ protected HttpInput newHttpInput()
+ {
+ return new HttpInputOverHTTP(this);
+ }
+
+ protected HttpChannelOverHttp newHttpChannel(HttpInput httpInput)
+ {
+ return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput);
+ }
+
+ protected HttpParser newHttpParser()
+ {
+ return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize());
+ }
+
+ protected HttpParser.RequestHandler newRequestHandler()
+ {
+ return _channel;
+ }
+
+ public Server getServer()
+ {
+ return _connector.getServer();
+ }
+
+ public Connector getConnector()
+ {
+ return _connector;
+ }
+
+ public HttpChannel> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public HttpParser getParser()
+ {
+ return _parser;
+ }
+
+ @Override
+ public int getMessagesIn()
+ {
+ return getHttpChannel().getRequests();
+ }
+
+ @Override
+ public int getMessagesOut()
+ {
+ return getHttpChannel().getRequests();
+ }
+
+ void releaseRequestBuffer()
+ {
+ if (_requestBuffer != null && !_requestBuffer.hasRemaining())
+ {
+ ByteBuffer buffer=_requestBuffer;
+ _requestBuffer=null;
+ _bufferPool.release(buffer);
+ }
+ }
+
+ public ByteBuffer getRequestBuffer()
+ {
+ if (_requestBuffer == null)
+ _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+ return _requestBuffer;
+ }
+
+ /**
+ * Parses and handles HTTP messages.
+ * This method is called when this {@link Connection} is ready to read bytes from the {@link EndPoint}.
+ * However, it can also be called if there is unconsumed data in the _requestBuffer, as a result of
+ * resuming a suspended request when there is a pipelined request already read into the buffer.
+ * This method fills bytes and parses them until either: EOF is filled; 0 bytes are filled;
+ * the HttpChannel finishes handling; or the connection has changed.
+ */
+ @Override
+ public void onFillable()
+ {
+ LOG.debug("{} onFillable {}", this, _channel.getState());
+
+ final HttpConnection last=setCurrentConnection(this);
+ int filled=Integer.MAX_VALUE;
+ boolean suspended=false;
+ try
+ {
+ // while not suspended and not upgraded
+ while (!suspended && getEndPoint().getConnection()==this)
+ {
+ // Do we need some data to parse
+ if (BufferUtil.isEmpty(_requestBuffer))
+ {
+ // If the previous iteration filled 0 bytes or saw a close, then break here
+ if (filled<=0)
+ break;
+
+ // Can we fill?
+ if(getEndPoint().isInputShutdown())
+ {
+ // No pretend we read -1
+ filled=-1;
+ _parser.atEOF();
+ }
+ else
+ {
+ // Get a buffer
+ // We are not in a race here for the request buffer as we have not yet received a request,
+ // so there are not an possible legal threads calling #parseContent or #completed.
+ _requestBuffer = getRequestBuffer();
+
+ // fill
+ filled = getEndPoint().fill(_requestBuffer);
+ if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+ filled = getEndPoint().fill(_requestBuffer);
+
+ // tell parser
+ if (filled < 0)
+ _parser.atEOF();
+ }
+ }
+
+ // Parse the buffer
+ if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+ {
+ // The parser returned true, which indicates the channel is ready to handle a request.
+ // Call the channel and this will either handle the request/response to completion OR,
+ // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+ // Not that onFillable no longer manipulates the request buffer from this point and that is
+ // left to threads calling #completed or #parseContent (which may be this thread inside handle())
+ suspended = !_channel.handle();
+ }
+ else
+ {
+ // We parsed what we could, recycle the request buffer
+ // We are not in a race here for the request buffer as we have not yet received a request,
+ // so there are not an possible legal threads calling #parseContent or #completed.
+ releaseRequestBuffer();
+ }
+ }
+ }
+ catch (EofException e)
+ {
+ LOG.debug(e);
+ }
+ catch (Exception e)
+ {
+ if (_parser.isIdle())
+ LOG.debug(e);
+ else
+ LOG.warn(this.toString(), e);
+ close();
+ }
+ finally
+ {
+ setCurrentConnection(last);
+ if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+ {
+ fillInterested();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Fill and parse data looking for content
+ * @throws IOException
+ */
+ protected void parseContent() throws IOException
+ {
+ // Not in a race here for the request buffer with #onFillable because an async consumer of
+ // content would only be started after onFillable has given up control.
+ // In a little bit of a race with #completed, but then not sure if it is legal to be doing
+ // async calls to IO and have a completed call at the same time.
+ ByteBuffer requestBuffer = getRequestBuffer();
+
+ while (_parser.inContentState())
+ {
+ // Can the parser progress (even with an empty buffer)
+ boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+ // No, we can we try reading some content?
+ if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
+ {
+ _parser.atEOF();
+ if (parsed)
+ break;
+ continue;
+ }
+
+ if (parsed)
+ break;
+
+ // OK lets read some data
+ int filled=getEndPoint().fill(requestBuffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("{} filled {}",this,filled);
+ if (filled<=0)
+ {
+ if (filled<0)
+ {
+ _parser.atEOF();
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void completed()
+ {
+ // Handle connection upgrades
+ if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+ {
+ Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+ if (connection != null)
+ {
+ LOG.debug("Upgrade from {} to {}", this, connection);
+ onClose();
+ getEndPoint().setConnection(connection);
+ connection.onOpen();
+ _channel.reset();
+ _parser.reset();
+ _generator.reset();
+ releaseRequestBuffer();
+ return;
+ }
+ }
+
+ // Finish consuming the request
+ // If we are still expecting
+ if (_channel.isExpecting100Continue())
+ // close to seek EOF
+ _parser.close();
+ else if (_parser.inContentState() && _generator.isPersistent())
+ // Complete reading the request
+ _channel.getRequest().getHttpInput().consumeAll();
+
+ // Reset the channel, parsers and generator
+ _channel.reset();
+ if (_generator.isPersistent() && !_parser.isClosed())
+ _parser.reset();
+ else
+ _parser.close();
+
+ // Not in a race here with onFillable, because it has given up control before calling handle.
+ // in a slight race with #completed, but not sure what to do with that anyway.
+ releaseRequestBuffer();
+ if (_chunk!=null)
+ _bufferPool.release(_chunk);
+ _chunk=null;
+ _generator.reset();
+
+ // if we are not called from the onfillable thread, schedule completion
+ if (getCurrentConnection()!=this)
+ {
+ // If we are looking for the next request
+ if (_parser.isStart())
+ {
+ // if the buffer is empty
+ if (BufferUtil.isEmpty(_requestBuffer))
+ {
+ // look for more data
+ fillInterested();
+ }
+ // else if we are still running
+ else if (getConnector().isRunning())
+ {
+ // Dispatched to handle a pipelined request
+ try
+ {
+ getExecutor().execute(this);
+ }
+ catch (RejectedExecutionException e)
+ {
+ if (getConnector().isRunning())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+ getEndPoint().close();
+ }
+ }
+ else
+ {
+ getEndPoint().close();
+ }
+ }
+ // else the parser must be closed, so seek the EOF if we are still open
+ else if (getEndPoint().isOpen())
+ fillInterested();
+ }
+ }
+
+ @Override
+ protected void onFillInterestedFailed(Throwable cause)
+ {
+ _parser.close();
+ super.onFillInterestedFailed(cause);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void run()
+ {
+ onFillable();
+ }
+
+
+ @Override
+ public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+ {
+ if (info==null)
+ new ContentCallback(content,lastContent,callback).iterate();
+ else
+ {
+ // If we are still expecting a 100 continues
+ if (_channel.isExpecting100Continue())
+ // then we can't be persistent
+ _generator.setPersistent(false);
+ new CommitCallback(info,content,lastContent,callback).iterate();
+ }
+ }
+
+ @Override
+ public void send(ByteBuffer content, boolean lastContent, Callback callback)
+ {
+ new ContentCallback(content,lastContent,callback).iterate();
+ }
+
+
+ protected class HttpChannelOverHttp extends HttpChannel
+ {
+ public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput input)
+ {
+ super(connector,config,endPoint,transport,input);
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ // If we have no request yet, just close
+ if (getRequest().getMethod()==null)
+ close();
+ else
+ super.earlyEOF();
+ }
+
+ @Override
+ public boolean content(ByteBuffer item)
+ {
+ super.content(item);
+ return true;
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ _generator.setPersistent(false);
+ super.badMessage(status,reason);
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ boolean persistent;
+ HttpVersion version = getHttpVersion();
+
+ switch (version)
+ {
+ case HTTP_0_9:
+ {
+ persistent = false;
+ break;
+ }
+ case HTTP_1_0:
+ {
+ persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+ if (!persistent)
+ persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+ if (persistent)
+ getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+ break;
+ }
+ case HTTP_1_1:
+ {
+ persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+ if (!persistent)
+ persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+ if (!persistent)
+ getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+ if (!persistent)
+ _generator.setPersistent(false);
+
+ return super.headerComplete();
+ }
+
+ @Override
+ protected void handleException(Throwable x)
+ {
+ _generator.setPersistent(false);
+ super.handleException(x);
+ }
+
+ @Override
+ public void failed()
+ {
+ getEndPoint().shutdownOutput();
+ }
+
+
+ @Override
+ public boolean messageComplete()
+ {
+ super.messageComplete();
+ return false;
+ }
+ }
+
+ private class CommitCallback extends IteratingCallback
+ {
+ final ByteBuffer _content;
+ final boolean _lastContent;
+ final ResponseInfo _info;
+ final Callback _callback;
+ ByteBuffer _header;
+
+ CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
+ {
+ _info=info;
+ _content=content;
+ _lastContent=last;
+ _callback=callback;
+ }
+
+ @Override
+ public Action process() throws Exception
+ {
+ ByteBuffer chunk = _chunk;
+ while (true)
+ {
+ HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} generate: {} ({},{},{})@{}",
+ this,
+ result,
+ BufferUtil.toSummaryString(_header),
+ BufferUtil.toSummaryString(_content),
+ _lastContent,
+ _generator.getState());
+
+ switch (result)
+ {
+ case NEED_HEADER:
+ {
+ // Look for optimisation to avoid allocating a _header buffer
+ /*
+ Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
+ if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
+ {
+ // use spare space in content buffer for header buffer
+ int p=_content.position();
+ int l=_content.limit();
+ _content.position(l);
+ _content.limit(l+_config.getResponseHeaderSize());
+ _header=_content.slice();
+ _header.limit(0);
+ _content.position(p);
+ _content.limit(l);
+ }
+ else
+ */
+ _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+
+ continue;
+ }
+ case NEED_CHUNK:
+ {
+ chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+ continue;
+ }
+ case FLUSH:
+ {
+ // Don't write the chunk or the content if this is a HEAD response
+ if (_channel.getRequest().isHead())
+ {
+ BufferUtil.clear(chunk);
+ BufferUtil.clear(_content);
+ }
+
+ // If we have a header
+ if (BufferUtil.hasContent(_header))
+ {
+ if (BufferUtil.hasContent(_content))
+ {
+ if (BufferUtil.hasContent(chunk))
+ getEndPoint().write(this, _header, chunk, _content);
+ else
+ getEndPoint().write(this, _header, _content);
+ }
+ else
+ getEndPoint().write(this, _header);
+ }
+ else if (BufferUtil.hasContent(chunk))
+ {
+ if (BufferUtil.hasContent(_content))
+ getEndPoint().write(this, chunk, _content);
+ else
+ getEndPoint().write(this, chunk);
+ }
+ else if (BufferUtil.hasContent(_content))
+ {
+ getEndPoint().write(this, _content);
+ }
+ else
+ continue;
+ return Action.SCHEDULED;
+ }
+ case SHUTDOWN_OUT:
+ {
+ getEndPoint().shutdownOutput();
+ continue;
+ }
+ case DONE:
+ {
+ if (_header!=null)
+ {
+ // don't release header in spare content buffer
+ if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() || _content.array()!=_header.array())
+ _bufferPool.release(_header);
+ }
+ return Action.SUCCEEDED;
+ }
+ case CONTINUE:
+ {
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException("generateResponse="+result);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ super.failed(x);
+ failedCallback(_callback,x);
+ }
+ }
+
+ private class ContentCallback extends IteratingCallback
+ {
+ final ByteBuffer _content;
+ final boolean _lastContent;
+ final Callback _callback;
+
+ ContentCallback(ByteBuffer content, boolean last, Callback callback)
+ {
+ _content=content;
+ _lastContent=last;
+ _callback=callback;
+ }
+
+ @Override
+ public Action process() throws Exception
+ {
+ ByteBuffer chunk = _chunk;
+ while (true)
+ {
+ HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} generate: {} ({},{})@{}",
+ this,
+ result,
+ BufferUtil.toSummaryString(_content),
+ _lastContent,
+ _generator.getState());
+
+ switch (result)
+ {
+ case NEED_HEADER:
+ throw new IllegalStateException();
+ case NEED_CHUNK:
+ {
+ chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+ continue;
+ }
+ case FLUSH:
+ {
+ // Don't write the chunk or the content if this is a HEAD response
+ if (_channel.getRequest().isHead())
+ {
+ BufferUtil.clear(chunk);
+ BufferUtil.clear(_content);
+ continue;
+ }
+ else if (BufferUtil.hasContent(chunk))
+ {
+ if (BufferUtil.hasContent(_content))
+ getEndPoint().write(this, chunk, _content);
+ else
+ getEndPoint().write(this, chunk);
+ }
+ else if (BufferUtil.hasContent(_content))
+ {
+ getEndPoint().write(this, _content);
+ }
+ else
+ continue;
+ return Action.SCHEDULED;
+ }
+ case SHUTDOWN_OUT:
+ {
+ getEndPoint().shutdownOutput();
+ continue;
+ }
+ case DONE:
+ {
+ return Action.SUCCEEDED;
+ }
+ case CONTINUE:
+ {
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException("generateResponse="+result);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ super.failed(x);
+ failedCallback(_callback,x);
+ }
+ }
+
+ @Override
+ public void abort()
+ {
+ // Do a direct close of the output, as this may indicate to a client that the
+ // response is bad either with RST or by abnormal completion of chunked response.
+ getEndPoint().close();
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java
new file mode 100644
index 00000000..ce6ffdb7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.Name;
+
+
+/* ------------------------------------------------------------ */
+/** A Connection Factory for HTTP Connections.
+ * Accepts connections either directly or via SSL and/or NPN chained connection factories. The accepted
+ * {@link HttpConnection}s are configured by a {@link HttpConfiguration} instance that is either created by
+ * default or passed in to the constructor.
+ */
+public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+ private final HttpConfiguration _config;
+
+ public HttpConnectionFactory()
+ {
+ this(new HttpConfiguration());
+ setInputBufferSize(16384);
+ }
+
+ public HttpConnectionFactory(@Name("config") HttpConfiguration config)
+ {
+ super(HttpVersion.HTTP_1_1.toString());
+ _config=config;
+ addBean(_config);
+ }
+
+ @Override
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _config;
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInput.java b/lib/jetty/org/eclipse/jetty/server/HttpInput.java
new file mode 100644
index 00000000..833ecded
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpInput.java
@@ -0,0 +1,503 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Objects;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
+ *
+ * Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
+ * maintains two states: the content state that tells whether there is content to consume and the EOF
+ * state that tells whether an EOF has arrived.
+ * Only once the content has been consumed the content state is moved to the EOF state.
+ */
+public abstract class HttpInput extends ServletInputStream implements Runnable
+{
+ private final static Logger LOG = Log.getLogger(HttpInput.class);
+
+ private final byte[] _oneByteBuffer = new byte[1];
+ private final Object _lock;
+ private HttpChannelState _channelState;
+ private ReadListener _listener;
+ private Throwable _onError;
+ private boolean _notReady;
+ private State _contentState = STREAM;
+ private State _eofState;
+ private long _contentRead;
+
+ protected HttpInput()
+ {
+ this(null);
+ }
+
+ protected HttpInput(Object lock)
+ {
+ _lock = lock == null ? this : lock;
+ }
+
+ public void init(HttpChannelState state)
+ {
+ synchronized (lock())
+ {
+ _channelState = state;
+ }
+ }
+
+ public final Object lock()
+ {
+ return _lock;
+ }
+
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ _listener = null;
+ _onError = null;
+ _notReady = false;
+ _contentState = STREAM;
+ _eofState = null;
+ _contentRead = 0;
+ }
+ }
+
+ @Override
+ public int available()
+ {
+ try
+ {
+ synchronized (lock())
+ {
+ T item = getNextContent();
+ return item == null ? 0 : remaining(item);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ int read = read(_oneByteBuffer, 0, 1);
+ return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ synchronized (lock())
+ {
+ T item = getNextContent();
+ if (item == null)
+ {
+ _contentState.waitForContent(this);
+ item = getNextContent();
+ if (item == null)
+ return _contentState.noContent();
+ }
+ int l = get(item, b, off, len);
+ _contentRead += l;
+ return l;
+ }
+ }
+
+ /**
+ * A convenience method to call nextContent and to check the return value, which if null then the
+ * a check is made for EOF and the state changed accordingly.
+ *
+ * @return Content or null if none available.
+ * @throws IOException
+ * @see #nextContent()
+ */
+ protected T getNextContent() throws IOException
+ {
+ T content = nextContent();
+ if (content == null)
+ {
+ synchronized (lock())
+ {
+ if (_eofState != null)
+ {
+ LOG.debug("{} eof {}", this, _eofState);
+ _contentState = _eofState;
+ }
+ }
+ }
+ return content;
+ }
+
+ /**
+ * Access the next content to be consumed from. Returning the next item does not consume it
+ * and it may be returned multiple times until it is consumed.
+ *
+ * Calls to {@link #get(Object, byte[], int, int)}
+ * or {@link #consume(Object, int)} are required to consume data from the content.
+ *
+ * @return the content or null if none available.
+ * @throws IOException if retrieving the content fails
+ */
+ protected abstract T nextContent() throws IOException;
+
+ /**
+ * @param item the content
+ * @return how many bytes remain in the given content
+ */
+ protected abstract int remaining(T item);
+
+ /**
+ * Copies the given content into the given byte buffer.
+ *
+ * @param item the content to copy from
+ * @param buffer the buffer to copy into
+ * @param offset the buffer offset to start copying from
+ * @param length the space available in the buffer
+ * @return the number of bytes actually copied
+ */
+ protected abstract int get(T item, byte[] buffer, int offset, int length);
+
+ /**
+ * Consumes the given content.
+ *
+ * @param item the content to consume
+ * @param length the number of bytes to consume
+ */
+ protected abstract void consume(T item, int length);
+
+ /**
+ * Blocks until some content or some end-of-file event arrives.
+ *
+ * @throws IOException if the wait is interrupted
+ */
+ protected abstract void blockForContent() throws IOException;
+
+ /**
+ * Adds some content to this input stream.
+ *
+ * @param item the content to add
+ */
+ public abstract void content(T item);
+
+ protected boolean onAsyncRead()
+ {
+ synchronized (lock())
+ {
+ if (_listener == null)
+ return false;
+ }
+ _channelState.onReadPossible();
+ return true;
+ }
+
+ public long getContentRead()
+ {
+ synchronized (lock())
+ {
+ return _contentRead;
+ }
+ }
+
+ /**
+ * This method should be called to signal that an EOF has been
+ * detected before all the expected content arrived.
+ *
+ * Typically this will result in an EOFException being thrown
+ * from a subsequent read rather than a -1 return.
+ */
+ public void earlyEOF()
+ {
+ synchronized (lock())
+ {
+ if (!isEOF())
+ {
+ LOG.debug("{} early EOF", this);
+ _eofState = EARLY_EOF;
+ if (_listener == null)
+ return;
+ }
+ }
+ _channelState.onReadPossible();
+ }
+
+ /**
+ * This method should be called to signal that all the expected
+ * content arrived.
+ */
+ public void messageComplete()
+ {
+ synchronized (lock())
+ {
+ if (!isEOF())
+ {
+ LOG.debug("{} EOF", this);
+ _eofState = EOF;
+ if (_listener == null)
+ return;
+ }
+ }
+ _channelState.onReadPossible();
+ }
+
+ public void consumeAll()
+ {
+ synchronized (lock())
+ {
+ try
+ {
+ while (!isFinished())
+ {
+ T item = getNextContent();
+ if (item == null)
+ _contentState.waitForContent(this);
+ else
+ consume(item, remaining(item));
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ }
+
+ public boolean isAsync()
+ {
+ synchronized (lock())
+ {
+ return _contentState==ASYNC;
+ }
+ }
+
+ /**
+ * @return whether an EOF has been detected, even though there may be content to consume.
+ */
+ public boolean isEOF()
+ {
+ synchronized (lock())
+ {
+ return _eofState != null && _eofState.isEOF();
+ }
+ }
+
+ @Override
+ public boolean isFinished()
+ {
+ synchronized (lock())
+ {
+ return _contentState.isEOF();
+ }
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ boolean finished;
+ synchronized (lock())
+ {
+ if (_contentState.isEOF())
+ return true;
+ if (_listener == null )
+ return true;
+ if (available() > 0)
+ return true;
+ if (_notReady)
+ return false;
+ _notReady = true;
+ finished = isFinished();
+ }
+ if (finished)
+ _channelState.onReadPossible();
+ else
+ unready();
+ return false;
+ }
+
+ protected void unready()
+ {
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener)
+ {
+ readListener = Objects.requireNonNull(readListener);
+ synchronized (lock())
+ {
+ if (_contentState != STREAM)
+ throw new IllegalStateException("state=" + _contentState);
+ _contentState = ASYNC;
+ _listener = readListener;
+ _notReady = true;
+ }
+ _channelState.onReadPossible();
+ }
+
+ public void failed(Throwable x)
+ {
+ synchronized (lock())
+ {
+ if (_onError != null)
+ LOG.warn(x);
+ else
+ _onError = x;
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ final Throwable error;
+ final ReadListener listener;
+ boolean available = false;
+ final boolean eof;
+
+ synchronized (lock())
+ {
+ if (!_notReady || _listener == null)
+ return;
+
+ error = _onError;
+ listener = _listener;
+
+ try
+ {
+ T item = getNextContent();
+ available = item != null && remaining(item) > 0;
+ }
+ catch (Exception e)
+ {
+ failed(e);
+ }
+
+ eof = !available && isFinished();
+ _notReady = !available && !eof;
+ }
+
+ try
+ {
+ if (error != null)
+ listener.onError(error);
+ else if (available)
+ listener.onDataAvailable();
+ else if (eof)
+ listener.onAllDataRead();
+ else
+ unready();
+ }
+ catch (Throwable e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ listener.onError(e);
+ }
+ }
+
+ protected static abstract class State
+ {
+ public void waitForContent(HttpInput> in) throws IOException
+ {
+ }
+
+ public int noContent() throws IOException
+ {
+ return -1;
+ }
+
+ public boolean isEOF()
+ {
+ return false;
+ }
+ }
+
+ protected static final State STREAM = new State()
+ {
+ @Override
+ public void waitForContent(HttpInput> input) throws IOException
+ {
+ input.blockForContent();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "STREAM";
+ }
+ };
+
+ protected static final State ASYNC = new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ return 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ASYNC";
+ }
+ };
+
+ protected static final State EARLY_EOF = new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ throw new EofException();
+ }
+
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "EARLY_EOF";
+ }
+ };
+
+ protected static final State EOF = new State()
+ {
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "EOF";
+ }
+ };
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java b/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java
new file mode 100644
index 00000000..35524500
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java
@@ -0,0 +1,145 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput implements Callback
+{
+ private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+ private final SharedBlockingCallback _readBlocker = new SharedBlockingCallback();
+ private final HttpConnection _httpConnection;
+ private ByteBuffer _content;
+
+ /**
+ * @param httpConnection
+ */
+ public HttpInputOverHTTP(HttpConnection httpConnection)
+ {
+ _httpConnection = httpConnection;
+ }
+
+ @Override
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ super.recycle();
+ _content=null;
+ }
+ }
+
+ @Override
+ protected void blockForContent() throws IOException
+ {
+ while(true)
+ {
+ try (Blocker blocker=_readBlocker.acquire())
+ {
+ _httpConnection.fillInterested(blocker);
+ LOG.debug("{} block readable on {}",this,blocker);
+ blocker.block();
+ }
+
+ Object content=getNextContent();
+ if (content!=null || isFinished())
+ break;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+ }
+
+ @Override
+ protected ByteBuffer nextContent() throws IOException
+ {
+ // If we have some content available, return it
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ // No - then we are going to need to parse some more content
+ _content=null;
+ _httpConnection.parseContent();
+
+ // If we have some content available, return it
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ return null;
+
+ }
+
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ public void content(ByteBuffer item)
+ {
+ if (BufferUtil.hasContent(_content))
+ throw new IllegalStateException();
+ _content=item;
+ }
+
+ @Override
+ protected void unready()
+ {
+ _httpConnection.fillInterested(this);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpOutput.java b/lib/jetty/org/eclipse/jetty/server/HttpOutput.java
new file mode 100644
index 00000000..7c0fd899
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/HttpOutput.java
@@ -0,0 +1,1096 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpOutput} implements {@link ServletOutputStream}
+ * as required by the Servlet specification.
+ * {@link HttpOutput} buffers content written by the application until a
+ * further write will overflow the buffer, at which point it triggers a commit
+ * of the response.
+ * {@link HttpOutput} can be closed and reopened, to allow requests included
+ * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
+ * close the stream, to be reopened after the inclusion ends.
+ */
+public class HttpOutput extends ServletOutputStream implements Runnable
+{
+ private static Logger LOG = Log.getLogger(HttpOutput.class);
+ private final HttpChannel> _channel;
+ private final SharedBlockingCallback _writeblock=new SharedBlockingCallback();
+ private long _written;
+ private ByteBuffer _aggregate;
+ private int _bufferSize;
+ private int _commitSize;
+ private WriteListener _writeListener;
+ private volatile Throwable _onError;
+
+ /*
+ ACTION OPEN ASYNC READY PENDING UNREADY CLOSED
+ -----------------------------------------------------------------------------------------------------
+ setWriteListener() READY->owp ise ise ise ise ise
+ write() OPEN ise PENDING wpe wpe eof
+ flush() OPEN ise PENDING wpe wpe eof
+ close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
+ isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
+ write completed - - - ASYNC READY->owp -
+
+ */
+ enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
+ private final AtomicReference _state=new AtomicReference<>(OutputState.OPEN);
+
+ public HttpOutput(HttpChannel> channel)
+ {
+ _channel = channel;
+ _bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
+ _commitSize=_bufferSize/4;
+ }
+
+ public HttpChannel> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public boolean isWritten()
+ {
+ return _written > 0;
+ }
+
+ public long getWritten()
+ {
+ return _written;
+ }
+
+ public void reset()
+ {
+ _written = 0;
+ reopen();
+ }
+
+ public void reopen()
+ {
+ _state.set(OutputState.OPEN);
+ }
+
+ public boolean isAllContentWritten()
+ {
+ return _channel.getResponse().isAllContentWritten(_written);
+ }
+
+ protected Blocker acquireWriteBlockingCallback() throws IOException
+ {
+ return _writeblock.acquire();
+ }
+
+ protected void write(ByteBuffer content, boolean complete) throws IOException
+ {
+ try (Blocker blocker=_writeblock.acquire())
+ {
+ write(content,complete,blocker);
+ blocker.block();
+ }
+ }
+
+ protected void write(ByteBuffer content, boolean complete, Callback callback)
+ {
+ _channel.write(content,complete,callback);
+ }
+
+ @Override
+ public void close()
+ {
+ loop: while(true)
+ {
+ OutputState state=_state.get();
+ switch (state)
+ {
+ case CLOSED:
+ break loop;
+
+ case UNREADY:
+ if (_state.compareAndSet(state,OutputState.ERROR))
+ _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
+ continue;
+
+ default:
+ if (_state.compareAndSet(state,OutputState.CLOSED))
+ {
+ try
+ {
+ write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
+ }
+ }
+ }
+ }
+
+ /* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
+ void closed()
+ {
+ loop: while(true)
+ {
+ OutputState state=_state.get();
+ switch (state)
+ {
+ case CLOSED:
+ break loop;
+
+ case UNREADY:
+ if (_state.compareAndSet(state,OutputState.ERROR))
+ _writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
+ continue;
+
+ default:
+ if (_state.compareAndSet(state,OutputState.CLOSED))
+ {
+ try
+ {
+ _channel.getResponse().closeOutput();
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
+ }
+ }
+ }
+ }
+
+ private void releaseBuffer()
+ {
+ if (_aggregate != null)
+ {
+ _channel.getConnector().getByteBufferPool().release(_aggregate);
+ _aggregate = null;
+ }
+ }
+
+ public boolean isClosed()
+ {
+ return _state.get()==OutputState.CLOSED;
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
+ return;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+ new AsyncFlush().iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ return;
+ }
+ break;
+ }
+ }
+
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ _written+=len;
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ // Should we aggregate?
+ if (!complete && len<=_commitSize)
+ {
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+
+ // YES - fill the aggregate with content from the buffer
+ int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+ // return if we are not complete, not full and filled all the content
+ if (filled==len && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // adjust offset/length
+ off+=filled;
+ len-=filled;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(b,off,len,complete).iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
+
+ // Should we aggregate?
+ int capacity = getBufferSize();
+ if (!complete && len<=_commitSize)
+ {
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+ // YES - fill the aggregate with content from the buffer
+ int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+ // return if we are not complete, not full and filled all the content
+ if (filled==len && !BufferUtil.isFull(_aggregate))
+ return;
+
+ // adjust offset/length
+ off+=filled;
+ len-=filled;
+ }
+
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ write(_aggregate, complete && len==0);
+
+ // should we fill aggregate again from the buffer?
+ if (len>0 && !complete && len<=_commitSize)
+ {
+ BufferUtil.append(_aggregate, b, off, len);
+ return;
+ }
+ }
+
+ // write any remaining content in the buffer directly
+ if (len>0)
+ {
+ ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
+ ByteBuffer view = wrap.duplicate();
+
+ // write a buffer capacity at a time to avoid JVM pooling large direct buffers
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
+ while (len>getBufferSize())
+ {
+ int p=view.position();
+ int l=p+getBufferSize();
+ view.limit(p+getBufferSize());
+ write(view,false);
+ len-=getBufferSize();
+ view.limit(l+Math.min(len,getBufferSize()));
+ view.position(l);
+ }
+ write(view,complete);
+ }
+ else if (complete)
+ write(BufferUtil.EMPTY_BUFFER,complete);
+
+ if (complete)
+ closed();
+
+ }
+
+ public void write(ByteBuffer buffer) throws IOException
+ {
+ _written+=buffer.remaining();
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(buffer,complete).iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
+ int len=BufferUtil.length(buffer);
+
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ write(_aggregate, complete && len==0);
+
+ // write any remaining content in the buffer directly
+ if (len>0)
+ write(buffer, complete);
+ else if (complete)
+ write(BufferUtil.EMPTY_BUFFER,complete);
+
+ if (complete)
+ closed();
+ }
+
+ @Override
+ public void write(int b) throws IOException
+ {
+ _written+=1;
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (complete || BufferUtil.isFull(_aggregate))
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ write(_aggregate, complete, blocker);
+ blocker.block();
+ }
+ if (complete)
+ closed();
+ }
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (!complete && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncFlush().iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void print(String s) throws IOException
+ {
+ if (isClosed())
+ throw new IOException("Closed");
+
+ write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param content The content to send.
+ * @throws IOException
+ */
+ public void sendContent(ByteBuffer content) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ write(content,true,blocker);
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param in The content to send
+ * @throws IOException
+ */
+ public void sendContent(InputStream in) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ new InputStreamWritingCB(in,blocker).iterate();
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param in The content to send
+ * @throws IOException
+ */
+ public void sendContent(ReadableByteChannel in) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ new ReadableByteChannelWritingCB(in,blocker).iterate();
+ blocker.block();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param content The content to send
+ * @throws IOException
+ */
+ public void sendContent(HttpContent content) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ sendContent(content,blocker);
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param content The content to send
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(ByteBuffer content, final Callback callback)
+ {
+ write(content,true,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ closed();
+ callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ callback.failed(x);
+ }
+ });
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param in The content to send as a stream. The stream will be closed
+ * after reading all content.
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(InputStream in, Callback callback)
+ {
+ new InputStreamWritingCB(in,callback).iterate();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param in The content to send as a channel. The channel will be closed
+ * after reading all content.
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(ReadableByteChannel in, Callback callback)
+ {
+ new ReadableByteChannelWritingCB(in,callback).iterate();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param httpContent The content to send
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(HttpContent httpContent, Callback callback) throws IOException
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ throw new IOException("written");
+ if (_channel.isCommitted())
+ throw new IOException("committed");
+
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
+ continue;
+ break;
+ case ERROR:
+ throw new EofException(_onError);
+ case CLOSED:
+ throw new EofException("Closed");
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
+ ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
+ if (buffer == null)
+ buffer = httpContent.getIndirectBuffer();
+
+ if (buffer!=null)
+ {
+ sendContent(buffer,callback);
+ return;
+ }
+
+ ReadableByteChannel rbc=httpContent.getReadableByteChannel();
+ if (rbc!=null)
+ {
+ // Close of the rbc is done by the async sendContent
+ sendContent(rbc,callback);
+ return;
+ }
+
+ InputStream in = httpContent.getInputStream();
+ if ( in!=null )
+ {
+ sendContent(in,callback);
+ return;
+ }
+
+ callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
+ }
+
+ public int getBufferSize()
+ {
+ return _bufferSize;
+ }
+
+ public void setBufferSize(int size)
+ {
+ _bufferSize = size;
+ _commitSize = size;
+ }
+
+ public void resetBuffer()
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ BufferUtil.clear(_aggregate);
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+ if (!_channel.getState().isAsync())
+ throw new IllegalStateException("!ASYNC");
+
+ if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
+ {
+ _writeListener = writeListener;
+ _channel.getState().onWritePossible();
+ }
+ else
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see javax.servlet.ServletOutputStream#isReady()
+ */
+ @Override
+ public boolean isReady()
+ {
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ return true;
+ case ASYNC:
+ if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
+ continue;
+ return true;
+ case READY:
+ return true;
+ case PENDING:
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
+ continue;
+ return false;
+ case UNREADY:
+ return false;
+
+ case ERROR:
+ return true;
+
+ case CLOSED:
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ loop: while (true)
+ {
+ OutputState state = _state.get();
+
+ if(_onError!=null)
+ {
+ switch(state)
+ {
+ case CLOSED:
+ case ERROR:
+ _onError=null;
+ break loop;
+
+ default:
+ if (_state.compareAndSet(state, OutputState.ERROR))
+ {
+ Throwable th=_onError;
+ _onError=null;
+ LOG.debug("onError",th);
+ _writeListener.onError(th);
+ close();
+
+ break loop;
+ }
+
+ }
+ continue loop;
+ }
+
+ switch(_state.get())
+ {
+ case READY:
+ case CLOSED:
+ // even though a write is not possible, because a close has
+ // occurred, we need to call onWritePossible to tell async
+ // producer that the last write completed.
+ try
+ {
+ _writeListener.onWritePossible();
+ break loop;
+ }
+ catch (Throwable e)
+ {
+ _onError=e;
+ }
+ break;
+ default:
+
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
+ }
+
+ private abstract class AsyncICB extends IteratingCallback
+ {
+ @Override
+ protected void completed()
+ {
+ while(true)
+ {
+ OutputState last=_state.get();
+ switch(last)
+ {
+ case PENDING:
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ continue;
+ break;
+
+ case UNREADY:
+ if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
+ continue;
+ _channel.getState().onWritePossible();
+ break;
+
+ case CLOSED:
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void failed(Throwable e)
+ {
+ super.failed(e);
+ _onError=e;
+ _channel.getState().onWritePossible();
+ }
+ }
+
+
+ private class AsyncFlush extends AsyncICB
+ {
+ protected volatile boolean _flushed;
+
+ public AsyncFlush()
+ {
+ }
+
+ @Override
+ protected Action process()
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ _flushed=true;
+ write(_aggregate, false, this);
+ return Action.SCHEDULED;
+ }
+
+ if (!_flushed)
+ {
+ _flushed=true;
+ write(BufferUtil.EMPTY_BUFFER,false,this);
+ return Action.SCHEDULED;
+ }
+
+ return Action.SUCCEEDED;
+ }
+ }
+
+
+
+ private class AsyncWrite extends AsyncICB
+ {
+ private final ByteBuffer _buffer;
+ private final ByteBuffer _slice;
+ private final boolean _complete;
+ private final int _len;
+ protected volatile boolean _completed;
+
+ public AsyncWrite(byte[] b, int off, int len, boolean complete)
+ {
+ _buffer=ByteBuffer.wrap(b, off, len);
+ _len=len;
+ // always use a view for large byte arrays to avoid JVM pooling large direct buffers
+ _slice=_len MAX_OUTPUT_CHARS)
+ {
+ write(s, offset, MAX_OUTPUT_CHARS);
+ offset += MAX_OUTPUT_CHARS;
+ length -= MAX_OUTPUT_CHARS;
+ }
+
+ s.getChars(offset, offset + length, _chars, 0);
+ write(_chars, 0, length);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ throw new AbstractMethodError();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java b/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java
new file mode 100644
index 00000000..cb81d3c9
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java
@@ -0,0 +1,230 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ *
+ *
+ * parses the following types of byte ranges:
+ *
+ * bytes=100-499
+ * bytes=-300
+ * bytes=100-
+ * bytes=1-2,2-3,6-,-2
+ *
+ * given an entity length, converts range to string
+ *
+ * bytes 100-499/500
+ *
+ *
+ *
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ *
+ * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
+ *
+ * @version $version$
+ *
+ */
+public class InclusiveByteRange
+{
+ private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
+
+ long first = 0;
+ long last = 0;
+
+ public InclusiveByteRange(long first, long last)
+ {
+ this.first = first;
+ this.last = last;
+ }
+
+ public long getFirst()
+ {
+ return first;
+ }
+
+ public long getLast()
+ {
+ return last;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param headers Enumeration of Range header fields.
+ * @param size Size of the resource.
+ * @return LazyList of satisfiable ranges
+ */
+ public static List satisfiableRanges(Enumeration headers, long size)
+ {
+ Object satRanges=null;
+
+ // walk through all Range headers
+ headers:
+ while (headers.hasMoreElements())
+ {
+ String header = (String) headers.nextElement();
+ StringTokenizer tok = new StringTokenizer(header,"=,",false);
+ String t=null;
+ try
+ {
+ // read all byte ranges for this header
+ while (tok.hasMoreTokens())
+ {
+ try
+ {
+ t = tok.nextToken().trim();
+
+ long first = -1;
+ long last = -1;
+ int d = t.indexOf('-');
+ if (d < 0 || t.indexOf("-",d + 1) >= 0)
+ {
+ if ("bytes".equals(t))
+ continue;
+ LOG.warn("Bad range format: {}",t);
+ continue headers;
+ }
+ else if (d == 0)
+ {
+ if (d + 1 < t.length())
+ last = Long.parseLong(t.substring(d + 1).trim());
+ else
+ {
+ LOG.warn("Bad range format: {}",t);
+ continue;
+ }
+ }
+ else if (d + 1 < t.length())
+ {
+ first = Long.parseLong(t.substring(0,d).trim());
+ last = Long.parseLong(t.substring(d + 1).trim());
+ }
+ else
+ first = Long.parseLong(t.substring(0,d).trim());
+
+ if (first == -1 && last == -1)
+ continue headers;
+
+ if (first != -1 && last != -1 && (first > last))
+ continue headers;
+
+ if (first < size)
+ {
+ InclusiveByteRange range = new InclusiveByteRange(first,last);
+ satRanges = LazyList.add(satRanges,range);
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ LOG.warn("Bad range format: {}",t);
+ LOG.ignore(e);
+ continue;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Bad range format: {}",t);
+ LOG.ignore(e);
+ }
+ }
+ return LazyList.getList(satRanges,true);
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getFirst(long size)
+ {
+ if (first<0)
+ {
+ long tf=size-last;
+ if (tf<0)
+ tf=0;
+ return tf;
+ }
+ return first;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getLast(long size)
+ {
+ if (first<0)
+ return size-1;
+
+ if (last<0 ||last>=size)
+ return size-1;
+ return last;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getSize(long size)
+ {
+ return getLast(size)-getFirst(size)+1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String toHeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes ");
+ sb.append(getFirst(size));
+ sb.append('-');
+ sb.append(getLast(size));
+ sb.append("/");
+ sb.append(size);
+ return sb.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String to416HeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes */");
+ sb.append(size);
+ return sb.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(60);
+ sb.append(Long.toString(first));
+ sb.append(":");
+ sb.append(Long.toString(last));
+ return sb.toString();
+ }
+
+
+}
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java
new file mode 100644
index 00000000..b4cdf8ff
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/**
+ */
+public class Iso88591HttpWriter extends HttpWriter
+{
+ /* ------------------------------------------------------------ */
+ public Iso88591HttpWriter(HttpOutput out)
+ {
+ super(out);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+ if (length==0 && out.isAllContentWritten())
+ {
+ close();
+ return;
+ }
+
+ if (length==1)
+ {
+ int c=s[offset];
+ out.write(c<256?c:'?');
+ return;
+ }
+
+ while (length > 0)
+ {
+ _bytes.reset();
+ int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+ byte[] buffer=_bytes.getBuf();
+ int bytes=_bytes.getCount();
+
+ if (chars>buffer.length-bytes)
+ chars=buffer.length-bytes;
+
+ for (int i = 0; i < chars; i++)
+ {
+ int c = s[offset+i];
+ buffer[bytes++]=(byte)(c<256?c:'?');
+ }
+ if (bytes>=0)
+ _bytes.setCount(bytes);
+
+ _bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LocalConnector.java b/lib/jetty/org/eclipse/jetty/server/LocalConnector.java
new file mode 100644
index 00000000..57962263
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/LocalConnector.java
@@ -0,0 +1,265 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class LocalConnector extends AbstractConnector
+{
+ private final BlockingQueue _connects = new LinkedBlockingQueue<>();
+
+
+ public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+ {
+ super(server,executor,scheduler,pool,acceptors,factories);
+ setIdleTimeout(30000);
+ }
+
+ public LocalConnector(Server server)
+ {
+ this(server, null, null, null, -1, new HttpConnectionFactory());
+ }
+
+ public LocalConnector(Server server, SslContextFactory sslContextFactory)
+ {
+ this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ }
+
+ public LocalConnector(Server server, ConnectionFactory connectionFactory)
+ {
+ this(server, null, null, null, -1, connectionFactory);
+ }
+
+ public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+ {
+ this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return this;
+ }
+
+ /** Sends requests and get responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ *
+ * This methods waits until the connection is closed or
+ * is idle for 1s before returning the responses.
+ * @param requests the requests
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public String getResponses(String requests) throws Exception
+ {
+ return getResponses(requests, 5, TimeUnit.SECONDS);
+ }
+
+ /** Sends requests and get responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ *
+ * This methods waits until the connection is closed or
+ * an idle period before returning the responses.
+ * @param requests the requests
+ * @param idleFor The time the response stream must be idle for before returning
+ * @param units The units of idleFor
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public String getResponses(String requests,long idleFor,TimeUnit units) throws Exception
+ {
+ ByteBuffer result = getResponses(BufferUtil.toBuffer(requests,StandardCharsets.UTF_8),idleFor,units);
+ return result==null?null:BufferUtil.toString(result,StandardCharsets.UTF_8);
+ }
+
+ /** Sends requests and get's responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ *
+ * This methods waits until the connection is closed or
+ * is idle for 1s before returning the responses.
+ * @param requestsBuffer the requests
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public ByteBuffer getResponses(ByteBuffer requestsBuffer) throws Exception
+ {
+ return getResponses(requestsBuffer, 5, TimeUnit.SECONDS);
+ }
+
+ /** Sends requests and get's responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ *
+ * This methods waits until the connection is closed or
+ * an idle period before returning the responses.
+ * @param requestsBuffer the requests
+ * @param idleFor The time the response stream must be idle for before returning
+ * @param units The units of idleFor
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
+ {
+ LOG.debug("requests {}", BufferUtil.toUTF8String(requestsBuffer));
+ LocalEndPoint endp = executeRequest(requestsBuffer);
+ endp.waitUntilClosedOrIdleFor(idleFor,units);
+ ByteBuffer responses = endp.takeOutput();
+ endp.getConnection().close();
+ LOG.debug("responses {}", BufferUtil.toUTF8String(responses));
+ return responses;
+ }
+
+ /**
+ * Execute a request and return the EndPoint through which
+ * responses can be received.
+ * @param rawRequest the request
+ * @return the local endpoint
+ */
+ public LocalEndPoint executeRequest(String rawRequest)
+ {
+ return executeRequest(BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8));
+ }
+
+ private LocalEndPoint executeRequest(ByteBuffer rawRequest)
+ {
+ LocalEndPoint endp = new LocalEndPoint();
+ endp.setInput(rawRequest);
+ _connects.add(endp);
+ return endp;
+ }
+
+ @Override
+ protected void accept(int acceptorID) throws IOException, InterruptedException
+ {
+ LOG.debug("accepting {}", acceptorID);
+ LocalEndPoint endPoint = _connects.take();
+ endPoint.onOpen();
+ onEndPointOpened(endPoint);
+
+ Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
+ endPoint.setConnection(connection);
+
+ connection.onOpen();
+ }
+
+ public class LocalEndPoint extends ByteArrayEndPoint
+ {
+ private final CountDownLatch _closed = new CountDownLatch(1);
+
+ public LocalEndPoint()
+ {
+ super(getScheduler(), LocalConnector.this.getIdleTimeout());
+ setGrowOutput(true);
+ }
+
+ public void addInput(String s)
+ {
+ // TODO this is a busy wait
+ while(getIn()==null || BufferUtil.hasContent(getIn()))
+ Thread.yield();
+ setInput(BufferUtil.toBuffer(s, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void close()
+ {
+ boolean wasOpen=isOpen();
+ super.close();
+ if (wasOpen)
+ {
+// connectionClosed(getConnection());
+ getConnection().onClose();
+ onClose();
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ LocalConnector.this.onEndPointClosed(this);
+ super.onClose();
+ _closed.countDown();
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ super.shutdownOutput();
+ close();
+ }
+
+ public void waitUntilClosed()
+ {
+ while (isOpen())
+ {
+ try
+ {
+ if (!_closed.await(10,TimeUnit.SECONDS))
+ break;
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+ public void waitUntilClosedOrIdleFor(long idleFor,TimeUnit units)
+ {
+ Thread.yield();
+ int size=getOutput().remaining();
+ while (isOpen())
+ {
+ try
+ {
+ if (!_closed.await(idleFor,units))
+ {
+ if (size==getOutput().remaining())
+ {
+ LOG.debug("idle for {} {}",idleFor,units);
+ return;
+ }
+ size=getOutput().remaining();
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java b/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java
new file mode 100644
index 00000000..1aad488e
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java
@@ -0,0 +1,350 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/** A monitor for low resources
+ *
An instance of this class will monitor all the connectors of a server (or a set of connectors
+ * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
+ * Low resources can be detected by:
+ * {@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
+ * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.
+ * If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
+ * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
+ * greater than {@link #getMaxMemory()}
+ * If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
+ * of connections exceeds {@link #getMaxConnections()}
+ *
+ *
+ * Once low resources state is detected, the cause is logged and all existing connections returned
+ * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
+ * to {@link #getLowResourcesIdleTimeout()}. New connections are not affected, however if the low
+ * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
+ * {@link #getLowResourcesIdleTimeout()} to all connections again. Once the low resources state is
+ * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
+ *
+ */
+@ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
+public class LowResourceMonitor extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
+ private final Server _server;
+ private Scheduler _scheduler;
+ private Connector[] _monitoredConnectors;
+ private int _period=1000;
+ private int _maxConnections;
+ private long _maxMemory;
+ private int _lowResourcesIdleTimeout=1000;
+ private int _maxLowResourcesTime=0;
+ private boolean _monitorThreads=true;
+ private final AtomicBoolean _low = new AtomicBoolean();
+ private String _cause;
+ private String _reasons;
+ private long _lowStarted;
+
+
+ private final Runnable _monitor = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (isRunning())
+ {
+ monitor();
+ _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+ }
+ }
+ };
+
+ public LowResourceMonitor(@Name("server") Server server)
+ {
+ _server=server;
+ }
+
+ @ManagedAttribute("Are the monitored connectors low on resources?")
+ public boolean isLowOnResources()
+ {
+ return _low.get();
+ }
+
+ @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
+ public String getLowResourcesReasons()
+ {
+ return _reasons;
+ }
+
+ @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
+ public long getLowResourcesStarted()
+ {
+ return _lowStarted;
+ }
+
+ @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
+ public Collection getMonitoredConnectors()
+ {
+ if (_monitoredConnectors==null)
+ return Collections.emptyList();
+ return Arrays.asList(_monitoredConnectors);
+ }
+
+ /**
+ * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
+ */
+ public void setMonitoredConnectors(Collection monitoredConnectors)
+ {
+ if (monitoredConnectors==null || monitoredConnectors.size()==0)
+ _monitoredConnectors=null;
+ else
+ _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
+ }
+
+ @ManagedAttribute("The monitor period in ms")
+ public int getPeriod()
+ {
+ return _period;
+ }
+
+ /**
+ * @param periodMS The period in ms to monitor for low resources
+ */
+ public void setPeriod(int periodMS)
+ {
+ _period = periodMS;
+ }
+
+ @ManagedAttribute("True if low available threads status is monitored")
+ public boolean getMonitorThreads()
+ {
+ return _monitorThreads;
+ }
+
+ /**
+ * @param monitorThreads If true, check connectors executors to see if they are
+ * {@link ThreadPool} instances that are low on threads.
+ */
+ public void setMonitorThreads(boolean monitorThreads)
+ {
+ _monitorThreads = monitorThreads;
+ }
+
+ @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
+ public int getMaxConnections()
+ {
+ return _maxConnections;
+ }
+
+ /**
+ * @param maxConnections The maximum connections before low resources state is triggered
+ */
+ public void setMaxConnections(int maxConnections)
+ {
+ _maxConnections = maxConnections;
+ }
+
+ @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered. Memory used is calculated as (totalMemory-freeMemory).")
+ public long getMaxMemory()
+ {
+ return _maxMemory;
+ }
+
+ /**
+ * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
+ */
+ public void setMaxMemory(long maxMemoryBytes)
+ {
+ _maxMemory = maxMemoryBytes;
+ }
+
+ @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
+ public int getLowResourcesIdleTimeout()
+ {
+ return _lowResourcesIdleTimeout;
+ }
+
+ /**
+ * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
+ */
+ public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
+ {
+ _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
+ }
+
+ @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
+ public int getMaxLowResourcesTime()
+ {
+ return _maxLowResourcesTime;
+ }
+
+ /**
+ * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
+ */
+ public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
+ {
+ _maxLowResourcesTime = maxLowResourcesTimeMS;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _scheduler = _server.getBean(Scheduler.class);
+
+ if (_scheduler==null)
+ {
+ _scheduler=new LRMScheduler();
+ _scheduler.start();
+ }
+ super.doStart();
+
+ _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ if (_scheduler instanceof LRMScheduler)
+ _scheduler.stop();
+ super.doStop();
+ }
+
+ protected Connector[] getMonitoredOrServerConnectors()
+ {
+ if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
+ return _monitoredConnectors;
+ return _server.getConnectors();
+ }
+
+ protected void monitor()
+ {
+ String reasons=null;
+ String cause="";
+ int connections=0;
+
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ connections+=connector.getConnectedEndPoints().size();
+
+ Executor executor = connector.getExecutor();
+ if (executor instanceof ThreadPool)
+ {
+ ThreadPool threadpool=(ThreadPool) executor;
+ if (_monitorThreads && threadpool.isLowOnThreads())
+ {
+ reasons=low(reasons,"Low on threads: "+threadpool);
+ cause+="T";
+ }
+ }
+ }
+
+ if (_maxConnections>0 && connections>_maxConnections)
+ {
+ reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
+ cause+="C";
+ }
+
+ long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
+ if (_maxMemory>0 && memory>_maxMemory)
+ {
+ reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
+ cause+="M";
+ }
+
+
+ if (reasons!=null)
+ {
+ // Log the reasons if there is any change in the cause
+ if (!cause.equals(_cause))
+ {
+ LOG.warn("Low Resources: {}",reasons);
+ _cause=cause;
+ }
+
+ // Enter low resources state?
+ if (_low.compareAndSet(false,true))
+ {
+ _reasons=reasons;
+ _lowStarted=System.currentTimeMillis();
+ setLowResources();
+ }
+
+ // Too long in low resources state?
+ if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
+ setLowResources();
+ }
+ else
+ {
+ if (_low.compareAndSet(true,false))
+ {
+ LOG.info("Low Resources cleared");
+ _reasons=null;
+ _lowStarted=0;
+ _cause=null;
+ clearLowResources();
+ }
+ }
+ }
+
+ protected void setLowResources()
+ {
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ for (EndPoint endPoint : connector.getConnectedEndPoints())
+ endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
+ }
+ }
+
+ protected void clearLowResources()
+ {
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ for (EndPoint endPoint : connector.getConnectedEndPoints())
+ endPoint.setIdleTimeout(connector.getIdleTimeout());
+ }
+ }
+
+ private String low(String reasons, String newReason)
+ {
+ if (reasons==null)
+ return newReason;
+ return reasons+", "+newReason;
+ }
+
+
+ private static class LRMScheduler extends ScheduledExecutorScheduler
+ {
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java
new file mode 100644
index 00000000..2c2286e2
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java
@@ -0,0 +1,281 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ */
+@ManagedObject("NCSA standard format request log")
+public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog
+{
+ private String _filename;
+ private boolean _append;
+ private int _retainDays;
+ private boolean _closeOut;
+ private String _filenameDateFormat = null;
+ private transient OutputStream _out;
+ private transient OutputStream _fileOut;
+ private transient Writer _writer;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create request log object with default settings.
+ */
+ public NCSARequestLog()
+ {
+ setExtended(true);
+ _append = true;
+ _retainDays = 31;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create request log object with specified output file name.
+ *
+ * @param filename the file name for the request log.
+ * This may be in the format expected
+ * by {@link RolloverFileOutputStream}
+ */
+ public NCSARequestLog(String filename)
+ {
+ setExtended(true);
+ _append = true;
+ _retainDays = 31;
+ setFilename(filename);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the output file name of the request log.
+ * The file name may be in the format expected by
+ * {@link RolloverFileOutputStream}.
+ *
+ * @param filename file name of the request log
+ *
+ */
+ public void setFilename(String filename)
+ {
+ if (filename != null)
+ {
+ filename = filename.trim();
+ if (filename.length() == 0)
+ filename = null;
+ }
+ _filename = filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the output file name of the request log.
+ *
+ * @return file name of the request log
+ */
+ @ManagedAttribute("file of log")
+ public String getFilename()
+ {
+ return _filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the file name of the request log with the expanded
+ * date wildcard if the output is written to the disk using
+ * {@link RolloverFileOutputStream}.
+ *
+ * @return file name of the request log, or null if not applicable
+ */
+ public String getDatedFilename()
+ {
+ if (_fileOut instanceof RolloverFileOutputStream)
+ return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected boolean isEnabled()
+ {
+ return (_fileOut != null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the number of days before rotated log files are deleted.
+ *
+ * @param retainDays number of days to keep a log file
+ */
+ public void setRetainDays(int retainDays)
+ {
+ _retainDays = retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the number of days before rotated log files are deleted.
+ *
+ * @return number of days to keep a log file
+ */
+ @ManagedAttribute("number of days that log files are kept")
+ public int getRetainDays()
+ {
+ return _retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set append to log flag.
+ *
+ * @param append true - request log file will be appended after restart,
+ * false - request log file will be overwritten after restart
+ */
+ public void setAppend(boolean append)
+ {
+ _append = append;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve append to log flag.
+ *
+ * @return value of the flag
+ */
+ @ManagedAttribute("existing log files are appends to the new one")
+ public boolean isAppend()
+ {
+ return _append;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the log file name date format.
+ * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
+ *
+ * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
+ */
+ public void setFilenameDateFormat(String logFileDateFormat)
+ {
+ _filenameDateFormat = logFileDateFormat;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the file name date format string.
+ *
+ * @return the log File Date Format
+ */
+ public String getFilenameDateFormat()
+ {
+ return _filenameDateFormat;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(String requestEntry) throws IOException
+ {
+ synchronized(this)
+ {
+ if (_writer==null)
+ return;
+ _writer.write(requestEntry);
+ _writer.write(StringUtil.__LINE_SEPARATOR);
+ _writer.flush();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set up request logging and open log file.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ if (_filename != null)
+ {
+ _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null);
+ _closeOut = true;
+ LOG.info("Opened " + getDatedFilename());
+ }
+ else
+ _fileOut = System.err;
+
+ _out = _fileOut;
+
+ synchronized(this)
+ {
+ _writer = new OutputStreamWriter(_out);
+ }
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Close the log file and perform cleanup.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ synchronized (this)
+ {
+ super.doStop();
+ try
+ {
+ if (_writer != null)
+ _writer.flush();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ if (_out != null && _closeOut)
+ try
+ {
+ _out.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+
+ _out = null;
+ _fileOut = null;
+ _closeOut = false;
+ _writer = null;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java
new file mode 100644
index 00000000..3d010699
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java
@@ -0,0 +1,163 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingServerConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
+
+ private final Connector connector;
+ private final SSLEngine engine;
+ private final List protocols;
+ private final String defaultProtocol;
+ private String protocol; // No need to be volatile: it is modified and read by the same thread
+
+ protected NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List protocols, String defaultProtocol)
+ {
+ super(endPoint, connector.getExecutor());
+ this.connector = connector;
+ this.protocols = protocols;
+ this.defaultProtocol = defaultProtocol;
+ this.engine = engine;
+ }
+
+ protected List getProtocols()
+ {
+ return protocols;
+ }
+
+ protected String getDefaultProtocol()
+ {
+ return defaultProtocol;
+ }
+
+ protected SSLEngine getSSLEngine()
+ {
+ return engine;
+ }
+
+ protected String getProtocol()
+ {
+ return protocol;
+ }
+
+ protected void setProtocol(String protocol)
+ {
+ this.protocol = protocol;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ int filled = fill();
+
+ if (filled == 0)
+ {
+ if (protocol == null)
+ {
+ if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+ {
+ // Here the SSL handshake is finished, but the protocol has not been negotiated.
+ LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
+ close();
+ }
+ else
+ {
+ // Here the SSL handshake is not finished yet but we filled 0 bytes,
+ // so we need to read more.
+ fillInterested();
+ }
+ }
+ else
+ {
+ ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
+ if (connectionFactory == null)
+ {
+ LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+ this, protocol, ConnectionFactory.class.getName());
+ close();
+ }
+ else
+ {
+ EndPoint endPoint = getEndPoint();
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+ LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
+ oldConnection.onClose();
+ endPoint.setConnection(newConnection);
+ getEndPoint().getConnection().onOpen();
+ }
+ }
+ }
+ else if (filled < 0)
+ {
+ // Something went bad, we need to close.
+ LOG.debug("{} closing on client close", this);
+ close();
+ }
+ else
+ {
+ // Must never happen, since we fill using an empty buffer
+ throw new IllegalStateException();
+ }
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ close();
+ return -1;
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ // Gentler close for SSL.
+ getEndPoint().shutdownOutput();
+ super.close();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
new file mode 100644
index 00000000..f826d711
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
@@ -0,0 +1,103 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+
+public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
+{
+ private final List protocols;
+ private String defaultProtocol;
+
+ public NegotiatingServerConnectionFactory(String protocol, String... protocols)
+ {
+ super(protocol);
+ this.protocols = Arrays.asList(protocols);
+ }
+
+ public String getDefaultProtocol()
+ {
+ return defaultProtocol;
+ }
+
+ public void setDefaultProtocol(String defaultProtocol)
+ {
+ this.defaultProtocol = defaultProtocol;
+ }
+
+ public List getProtocols()
+ {
+ return protocols;
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ List protocols = this.protocols;
+ if (protocols.isEmpty())
+ {
+ protocols = connector.getProtocols();
+ Iterator i = protocols.iterator();
+ while (i.hasNext())
+ {
+ String protocol = i.next();
+ String prefix = "ssl-";
+ if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
+ {
+ i.remove();
+ }
+ }
+ }
+
+ String dft = defaultProtocol;
+ if (dft == null && !protocols.isEmpty())
+ dft = protocols.get(0);
+
+ SSLEngine engine = null;
+ EndPoint ep = endPoint;
+ while (engine == null && ep != null)
+ {
+ // TODO make more generic
+ if (ep instanceof SslConnection.DecryptedEndPoint)
+ engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
+ else
+ ep = null;
+ }
+
+ return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
+ }
+
+ protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List protocols, String defaultProtocol);
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java
new file mode 100644
index 00000000..58fdc988
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * A {@link Connector} for TCP/IP network connectors
+ */
+public interface NetworkConnector extends Connector, Closeable
+{
+ /**
+ * Performs the activities needed to open the network communication
+ * (for example, to start accepting incoming network connections).
+ *
+ * @throws IOException if this connector cannot be opened
+ * @see #close()
+ */
+ void open() throws IOException;
+
+ /**
+ * Performs the activities needed to close the network communication
+ * (for example, to stop accepting network connections).
+ * Once a connector has been closed, it cannot be opened again without first
+ * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()}
+ */
+ @Override
+ void close();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * A Connector may be opened and not started (to reserve a port)
+ * or closed and running (to allow graceful shutdown of existing connections)
+ * @return True if the connector is Open.
+ */
+ boolean isOpen();
+
+ /**
+ * @return The hostname representing the interface to which
+ * this connector will bind, or null for all interfaces.
+ */
+ String getHost();
+
+ /**
+ * @return The configured port for the connector or 0 if any available
+ * port may be used.
+ */
+ int getPort();
+
+ /**
+ * @return The actual port the connector is listening on, or
+ * -1 if it has not been opened, or -2 if it has been closed.
+ */
+ int getLocalPort();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
new file mode 100644
index 00000000..34de615b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.
+ * {@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link java.util.ConcurrentModificationException}s.
+ */
+public class NetworkTrafficServerConnector extends ServerConnector
+{
+ private final List listeners = new CopyOnWriteArrayList<>();
+
+ public NetworkTrafficServerConnector(Server server)
+ {
+ this(server, null, null, null, 0, 0, new HttpConnectionFactory());
+ }
+
+ public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+ {
+ super(server, sslContextFactory, connectionFactory);
+ }
+
+ public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
+ {
+ super(server, connectionFactory);
+ }
+
+ public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+ {
+ super(server, executor, scheduler, pool, acceptors, selectors, factories);
+ }
+
+ public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
+ {
+ super(server, sslContextFactory);
+ }
+
+ /**
+ * @param listener the listener to add
+ */
+ public void addNetworkTrafficListener(NetworkTrafficListener listener)
+ {
+ listeners.add(listener);
+ }
+
+ /**
+ * @param listener the listener to remove
+ */
+ public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+ {
+ listeners.remove(listener);
+ }
+
+ @Override
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
+ {
+ NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
+ return endPoint;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java
new file mode 100644
index 00000000..881fbfa0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java
@@ -0,0 +1,143 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
+ *
+ * {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.
+ *
+ * To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
+ * that can be implemented so that the caller will know when buffers are queued and consumed.
+ */
+public abstract class QueuedHttpInput extends HttpInput
+{
+ private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+ private final ArrayQueue _inputQ = new ArrayQueue<>(lock());
+
+ public QueuedHttpInput()
+ {
+ }
+
+ public void content(T item)
+ {
+ // The buffer is not copied here. This relies on the caller not recycling the buffer
+ // until the it is consumed. The onContentConsumed and onAllContentConsumed() callbacks are
+ // the signals to the caller that the buffers can be recycled.
+
+ synchronized (lock())
+ {
+ boolean wasEmpty = _inputQ.isEmpty();
+ _inputQ.add(item);
+ LOG.debug("{} queued {}", this, item);
+ if (wasEmpty)
+ {
+ if (!onAsyncRead())
+ lock().notify();
+ }
+ }
+ }
+
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ T item = _inputQ.pollUnsafe();
+ while (item != null)
+ {
+ onContentConsumed(item);
+ item = _inputQ.pollUnsafe();
+ }
+ super.recycle();
+ }
+ }
+
+ @Override
+ protected T nextContent()
+ {
+ synchronized (lock())
+ {
+ // Items are removed only when they are fully consumed.
+ T item = _inputQ.peekUnsafe();
+ // Skip consumed items at the head of the queue.
+ while (item != null && remaining(item) == 0)
+ {
+ _inputQ.pollUnsafe();
+ onContentConsumed(item);
+ LOG.debug("{} consumed {}", this, item);
+ item = _inputQ.peekUnsafe();
+ }
+ return item;
+ }
+ }
+
+ protected void blockForContent() throws IOException
+ {
+ synchronized (lock())
+ {
+ while (_inputQ.isEmpty() && !isFinished() && !isEOF())
+ {
+ try
+ {
+ LOG.debug("{} waiting for content", this);
+ lock().wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw (IOException)new InterruptedIOException().initCause(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback that signals that the given content has been consumed.
+ *
+ * @param item the consumed content
+ */
+ protected abstract void onContentConsumed(T item);
+
+ public void earlyEOF()
+ {
+ synchronized (lock())
+ {
+ super.earlyEOF();
+ lock().notify();
+ }
+ }
+
+ public void messageComplete()
+ {
+ synchronized (lock())
+ {
+ super.messageComplete();
+ lock().notify();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QuietServletException.java b/lib/jetty/org/eclipse/jetty/server/QuietServletException.java
new file mode 100644
index 00000000..5221d638
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/QuietServletException.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletException;
+
+
+/* ------------------------------------------------------------ */
+/** A ServletException that is logged less verbosely than
+ * a normal ServletException.
+ *
+ * Used for container generated exceptions that need only a message rather
+ * than a stack trace.
+ *
+ */
+public class QuietServletException extends ServletException
+{
+ public QuietServletException()
+ {
+ super();
+ }
+
+ public QuietServletException(String message, Throwable rootCause)
+ {
+ super(message,rootCause);
+ }
+
+ public QuietServletException(String message)
+ {
+ super(message);
+ }
+
+ public QuietServletException(Throwable rootCause)
+ {
+ super(rootCause);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Request.java b/lib/jetty/org/eclipse/jetty/server/Request.java
new file mode 100644
index 00000000..77c80abc
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/server/Request.java
@@ -0,0 +1,2265 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty Request.
+ *
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the javax.servlet.http
package.
+ *
+ *
+ * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
+ * request object to be as lightweight as possible and not actually implement any significant behavior. For example
+ *
+ *
+ * The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
+ * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.
+ *
+ * the HTTP session methods will all return null sessions until such time as a request has been passed to a
+ * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.
+ *
+ * The {@link Request#getServletPath()} method will return null until the request has been passed to a org.eclipse.jetty.servlet.ServletHandler
+ * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.
+ *
+ *
+ * A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
+ * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
+ *
+ *
+ * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
+ * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
+ * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
+ * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+ public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+ public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+ public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
+
+ private static final Logger LOG = Log.getLogger(Request.class);
+ private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
+ private static final int __NONE = 0, _STREAM = 1, __READER = 2;
+
+ private final HttpChannel> _channel;
+ private final HttpFields _fields=new HttpFields();
+ private final List _requestAttributeListeners=new ArrayList<>();
+ private final HttpInput> _input;
+
+ public static class MultiPartCleanerListener implements ServletRequestListener
+ {
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ //Clean up any tmp files created by MultiPartInputStream
+ MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+ if (mpis != null)
+ {
+ ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
+
+ //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
+ if (context == sre.getServletContext())
+ {
+ try
+ {
+ mpis.deleteParts();
+ }
+ catch (MultiException e)
+ {
+ sre.getServletContext().log("Errors deleting multipart tmp files", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ //nothing to do, multipart config set up by ServletHolder.handle()
+ }
+
+ }
+
+
+
+ private boolean _secure;
+ private boolean _asyncSupported = true;
+ private boolean _newContext;
+ private boolean _cookiesExtracted = false;
+ private boolean _handled = false;
+ private boolean _paramsExtracted;
+ private boolean _requestedSessionIdFromCookie = false;
+ private volatile Attributes _attributes;
+ private Authentication _authentication;
+ private String _characterEncoding;
+ private ContextHandler.Context _context;
+ private String _contextPath;
+ private CookieCutter _cookies;
+ private DispatcherType _dispatcherType;
+ private int _inputState = __NONE;
+ private HttpMethod _httpMethod;
+ private String _httpMethodString;
+ private MultiMap _queryParameters;
+ private MultiMap _contentParameters;
+ private MultiMap _parameters;
+ private String _pathInfo;
+ private int _port;
+ private HttpVersion _httpVersion = HttpVersion.HTTP_1_1;
+ private String _queryEncoding;
+ private String _queryString;
+ private BufferedReader _reader;
+ private String _readerEncoding;
+ private InetSocketAddress _remote;
+ private String _requestedSessionId;
+ private String _requestURI;
+ private Map _savedNewSessions;
+ private String _scheme = URIUtil.HTTP;
+ private UserIdentity.Scope _scope;
+ private String _serverName;
+ private String _servletPath;
+ private HttpSession _session;
+ private SessionManager _sessionManager;
+ private long _timeStamp;
+ private HttpURI _uri;
+ private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
+ private AsyncContextState _async;
+
+ /* ------------------------------------------------------------ */
+ public Request(HttpChannel> channel, HttpInput> input)
+ {
+ _channel = channel;
+ _input = input;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpFields getHttpFields()
+ {
+ return _fields;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpInput> getHttpInput()
+ {
+ return _input;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addEventListener(final EventListener listener)
+ {
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+ if (listener instanceof AsyncListener)
+ throw new IllegalArgumentException(listener.getClass().toString());
+ }
+
+ public void extractParameters()
+ {
+ if (_paramsExtracted)
+ return;
+
+ _paramsExtracted = true;
+
+ // Extract query string parameters; these may be replaced by a forward()
+ // and may have already been extracted by mergeQueryParameters().
+ if (_queryParameters == null)
+ _queryParameters = extractQueryParameters();
+
+ // Extract content parameters; these cannot be replaced by a forward()
+ // once extracted and may have already been extracted by getParts() or
+ // by a processing happening after a form-based authentication.
+ if (_contentParameters == null)
+ _contentParameters = extractContentParameters();
+
+ _parameters = restoreParameters();
+ }
+
+ private MultiMap extractQueryParameters()
+ {
+ MultiMap result = new MultiMap<>();
+ if (_uri != null && _uri.hasQuery())
+ {
+ if (_queryEncoding == null)
+ {
+ _uri.decodeQueryTo(result);
+ }
+ else
+ {
+ try
+ {
+ _uri.decodeQueryTo(result, _queryEncoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+ }
+ return result;
+ }
+
+ private MultiMap extractContentParameters()
+ {
+ MultiMap result = new MultiMap<>();
+
+ String contentType = getContentType();
+ if (contentType != null && !contentType.isEmpty())
+ {
+ contentType = HttpFields.valueParameters(contentType, null);
+ int contentLength = getContentLength();
+ if (contentLength != 0)
+ {
+ if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
+ (HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
+ {
+ extractFormParameters(result);
+ }
+ else if (contentType.startsWith("multipart/form-data") &&
+ getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
+ _multiPartInputStream == null)
+ {
+ extractMultipartParameters(result);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void extractFormParameters(MultiMap params)
+ {
+ try
+ {
+ int maxFormContentSize = -1;
+ int maxFormKeys = -1;
+
+ if (_context != null)
+ {
+ maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+ maxFormKeys = _context.getContextHandler().getMaxFormKeys();
+ }
+
+ if (maxFormContentSize < 0)
+ {
+ Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+ if (obj == null)
+ maxFormContentSize = 200000;
+ else if (obj instanceof Number)
+ {
+ Number size = (Number)obj;
+ maxFormContentSize = size.intValue();
+ }
+ else if (obj instanceof String)
+ {
+ maxFormContentSize = Integer.valueOf((String)obj);
+ }
+ }
+
+ if (maxFormKeys < 0)
+ {
+ Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+ if (obj == null)
+ maxFormKeys = 1000;
+ else if (obj instanceof Number)
+ {
+ Number keys = (Number)obj;
+ maxFormKeys = keys.intValue();
+ }
+ else if (obj instanceof String)
+ {
+ maxFormKeys = Integer.valueOf((String)obj);
+ }
+ }
+
+ int contentLength = getContentLength();
+ if (contentLength > maxFormContentSize && maxFormContentSize > 0)
+ {
+ throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
+ }
+ InputStream in = getInputStream();
+ if (_input.isAsync())
+ throw new IllegalStateException("Cannot extract parameters with async IO");
+
+ UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
+ }
+ catch (IOException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+
+ private void extractMultipartParameters(MultiMap result)
+ {
+ try
+ {
+ getParts(result);
+ }
+ catch (IOException | ServletException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null || state.isInitial() && !state.isAsync())
+ throw new IllegalStateException(state.getStatusString());
+
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpChannelState getHttpChannelState()
+ {
+ return _channel.getState();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request Attribute.
+ * Also supports jetty specific attributes to gain access to Jetty APIs:
+ *
+ * org.eclipse.jetty.server.Server The Jetty Server instance
+ * org.eclipse.jetty.server.HttpChannel The HttpChannel for this request
+ * org.eclipse.jetty.server.HttpConnection The HttpConnection or null if another transport is used
+ *
+ * While these attributes may look like security problems, they are exposing nothing that is not already
+ * available via reflection from a Request instance.
+ *
+ * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+ */
+ @Override
+ public Object getAttribute(String name)
+ {
+ if (name.startsWith("org.eclipse.jetty"))
+ {
+ if ("org.eclipse.jetty.server.Server".equals(name))
+ return _channel.getServer();
+ if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+ return _channel;
+ if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+ _channel.getHttpTransport() instanceof HttpConnection)
+ return _channel.getHttpTransport();
+ }
+ return (_attributes == null)?null:_attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getAttributeNames()
+ */
+ @Override
+ public Enumeration getAttributeNames()
+ {
+ if (_attributes == null)
+ return Collections.enumeration(Collections.emptyList());
+
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Attributes getAttributes()
+ {
+ if (_attributes == null)
+ _attributes = new AttributesMap();
+ return _attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the authentication.
+ *
+ * @return the authentication
+ */
+ public Authentication getAuthentication()
+ {
+ return _authentication;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getAuthType()
+ */
+ @Override
+ public String getAuthType()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getAuthMethod();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getCharacterEncoding()
+ */
+ @Override
+ public String getCharacterEncoding()
+ {
+ return _characterEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connection.
+ */
+ public HttpChannel> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentLength()
+ */
+ @Override
+ public int getContentLength()
+ {
+ return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest.getContentLengthLong()
+ */
+ @Override
+ public long getContentLengthLong()
+ {
+ return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _input.getContentRead();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ @Override
+ public String getContentType()
+ {
+ return _fields.getStringField(HttpHeader.CONTENT_TYPE);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The current {@link Context context} used for this request, or null
if {@link #setContext} has not yet been called.
+ */
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getContextPath()
+ */
+ @Override
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getCookies()
+ */
+ @Override
+ public Cookie[] getCookies()
+ {
+ if (_cookiesExtracted)
+ {
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
+ }
+
+ _cookiesExtracted = true;
+
+ Enumeration> enm = _fields.getValues(HttpHeader.COOKIE.toString());
+
+ // Handle no cookies
+ if (enm != null)
+ {
+ if (_cookies == null)
+ _cookies = new CookieCutter();
+
+ while (enm.hasMoreElements())
+ {
+ String c = (String)enm.nextElement();
+ _cookies.addCookieField(c);
+ }
+ }
+
+ //Javadoc for Request.getCookies() stipulates null for no cookies
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+ */
+ @Override
+ public long getDateHeader(String name)
+ {
+ return _fields.getDateField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public DispatcherType getDispatcherType()
+ {
+ return _dispatcherType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+ */
+ @Override
+ public String getHeader(String name)
+ {
+ return _fields.getStringField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+ */
+ @Override
+ public Enumeration getHeaderNames()
+ {
+ return _fields.getFieldNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+ */
+ @Override
+ public Enumeration getHeaders(String name)
+ {
+ Enumeration e = _fields.getValues(name);
+ if (e == null)
+ return Collections.enumeration(Collections.emptyList());
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the inputState.
+ */
+ public int getInputState()
+ {
+ return _inputState;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getInputStream()
+ */
+ @Override
+ public ServletInputStream getInputStream() throws IOException
+ {
+ if (_inputState != __NONE && _inputState != _STREAM)
+ throw new IllegalStateException("READER");
+ _inputState = _STREAM;
+
+ if (_channel.isExpecting100Continue())
+ _channel.continue100(_input.available());
+
+ return _input;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+ */
+ @Override
+ public int getIntHeader(String name)
+ {
+ return (int)_fields.getLongField(name);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocale()
+ */
+ @Override
+ public Locale getLocale()
+ {
+ Enumeration enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Locale.getDefault();
+
+ // sort the list in quality order
+ List> acceptLanguage = HttpFields.qualityList(enm);
+ if (acceptLanguage.size() == 0)
+ return Locale.getDefault();
+
+ int size = acceptLanguage.size();
+
+ if (size > 0)
+ {
+ String language = (String)acceptLanguage.get(0);
+ language = HttpFields.valueParameters(language,null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0,dash).trim();
+ }
+ return new Locale(language,country);
+ }
+
+ return Locale.getDefault();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocales()
+ */
+ @Override
+ public Enumeration getLocales()
+ {
+
+ Enumeration enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Collections.enumeration(__defaultLocale);
+
+ // sort the list in quality order
+ List acceptLanguage = HttpFields.qualityList(enm);
+
+ if (acceptLanguage.size() == 0)
+ return Collections.enumeration(__defaultLocale);
+
+ List langs = new ArrayList<>();
+
+ // convert to locals
+ for (String language : acceptLanguage)
+ {
+ language = HttpFields.valueParameters(language, null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0, dash).trim();
+ }
+ langs.add(new Locale(language, country));
+ }
+
+ if (langs.size() == 0)
+ return Collections.enumeration(__defaultLocale);
+
+ return Collections.enumeration(langs);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalAddr()
+ */
+ @Override
+ public String getLocalAddr()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ if (local==null)
+ return "";
+ InetAddress address = local.getAddress();
+ if (address==null)
+ return local.getHostString();
+ return address.getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalName()
+ */
+ @Override
+ public String getLocalName()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ return local.getHostString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalPort()
+ */
+ @Override
+ public int getLocalPort()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ return local.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getMethod()
+ */
+ @Override
+ public String getMethod()
+ {
+ return _httpMethodString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+ */
+ @Override
+ public String getParameter(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return _parameters.getValue(name,0);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterMap()
+ */
+ @Override
+ public Map getParameterMap()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterNames()
+ */
+ @Override
+ public Enumeration getParameterNames()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return Collections.enumeration(_parameters.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+ */
+ @Override
+ public String[] getParameterValues(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ List vals = _parameters.getValues(name);
+ if (vals == null)
+ return null;
+ return vals.toArray(new String[vals.size()]);
+ }
+
+ private MultiMap restoreParameters()
+ {
+ MultiMap result = new MultiMap<>();
+ if (_queryParameters == null)
+ _queryParameters = extractQueryParameters();
+ result.addAllValues(_queryParameters);
+ result.addAllValues(_contentParameters);
+ return result;
+ }
+
+ public MultiMap getQueryParameters()
+ {
+ return _queryParameters;
+ }
+
+ public void setQueryParameters(MultiMap queryParameters)
+ {
+ _queryParameters = queryParameters;
+ }
+
+ public void setContentParameters(MultiMap contentParameters)
+ {
+ _contentParameters = contentParameters;
+ }
+
+ public void resetParameters()
+ {
+ _parameters = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+ */
+ @Override
+ public String getPathInfo()
+ {
+ return _pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+ */
+ @Override
+ public String getPathTranslated()
+ {
+ if (_pathInfo == null || _context == null)
+ return null;
+ return _context.getRealPath(_pathInfo);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getProtocol()
+ */
+ @Override
+ public String getProtocol()
+ {
+ return _httpVersion.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getProtocol()
+ */
+ public HttpVersion getHttpVersion()
+ {
+ return _httpVersion;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getQueryEncoding()
+ {
+ return _queryEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getQueryString()
+ */
+ @Override
+ public String getQueryString()
+ {
+ if (_queryString == null && _uri != null)
+ {
+ if (_queryEncoding == null)
+ _queryString = _uri.getQuery();
+ else
+ _queryString = _uri.getQuery(_queryEncoding);
+ }
+ return _queryString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getReader()
+ */
+ @Override
+ public BufferedReader getReader() throws IOException
+ {
+ if (_inputState != __NONE && _inputState != __READER)
+ throw new IllegalStateException("STREAMED");
+
+ if (_inputState == __READER)
+ return _reader;
+
+ String encoding = getCharacterEncoding();
+ if (encoding == null)
+ encoding = StringUtil.__ISO_8859_1;
+
+ if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
+ {
+ final ServletInputStream in = getInputStream();
+ _readerEncoding = encoding;
+ _reader = new BufferedReader(new InputStreamReader(in,encoding))
+ {
+ @Override
+ public void close() throws IOException
+ {
+ in.close();
+ }
+ };
+ }
+ _inputState = __READER;
+ return _reader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+ */
+ @Override
+ public String getRealPath(String path)
+ {
+ if (_context == null)
+ return null;
+ return _context.getRealPath(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Access the underlying Remote {@link InetSocketAddress} for this request.
+ *
+ * @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
+ * conditions that result in no remote address)
+ */
+ public InetSocketAddress getRemoteInetSocketAddress()
+ {
+ InetSocketAddress remote = _remote;
+ if (remote == null)
+ remote = _channel.getRemoteAddress();
+
+ return remote;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteAddr()
+ */
+ @Override
+ public String getRemoteAddr()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+
+ if (remote==null)
+ return "";
+
+ InetAddress address = remote.getAddress();
+ if (address==null)
+ return remote.getHostString();
+
+ return address.getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteHost()
+ */
+ @Override
+ public String getRemoteHost()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+ return remote==null?"":remote.getHostString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemotePort()
+ */
+ @Override
+ public int getRemotePort()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+ return remote==null?0:remote.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+ */
+ @Override
+ public String getRemoteUser()
+ {
+ Principal p = getUserPrincipal();
+ if (p == null)
+ return null;
+ return p.getName();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+ */
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ if (path == null || _context == null)
+ return null;
+
+ // handle relative path
+ if (!path.startsWith("/"))
+ {
+ String relTo = URIUtil.addPaths(_servletPath,_pathInfo);
+ int slash = relTo.lastIndexOf("/");
+ if (slash > 1)
+ relTo = relTo.substring(0,slash + 1);
+ else
+ relTo = "/";
+ path = URIUtil.addPaths(relTo,path);
+ }
+
+ return _context.getRequestDispatcher(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+ */
+ @Override
+ public String getRequestedSessionId()
+ {
+ return _requestedSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+ */
+ @Override
+ public String getRequestURI()
+ {
+ if (_requestURI == null && _uri != null)
+ _requestURI = _uri.getPathAndParam();
+ return _requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+ */
+ @Override
+ public StringBuffer getRequestURL()
+ {
+ final StringBuffer url = new StringBuffer(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+ url.append(getRequestURI());
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Response getResponse()
+ {
+ return _channel.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
+ * path.
+ *
+ * Because this method returns a StringBuffer
, not a string, you can modify the URL easily, for example, to append path and query parameters.
+ *
+ * This method is useful for creating redirect messages and for reporting errors.
+ *
+ * @return "scheme://host:port"
+ */
+ public StringBuilder getRootURL()
+ {
+ StringBuilder url = new StringBuilder(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getScheme()
+ */
+ @Override
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerName()
+ */
+ @Override
+ public String getServerName()
+ {
+ // Return already determined host
+ if (_serverName != null)
+ return _serverName;
+
+ if (_uri == null)
+ throw new IllegalStateException("No uri");
+
+ // Return host from absolute URI
+ _serverName = _uri.getHost();
+ if (_serverName != null)
+ {
+ _port = _uri.getPort();
+ return _serverName;
+ }
+
+ // Return host from header field
+ String hostPort = _fields.getStringField(HttpHeader.HOST);
+
+ _port=0;
+ if (hostPort != null)
+ {
+ int len=hostPort.length();
+ loop: for (int i = len; i-- > 0;)
+ {
+ char c2 = (char)(0xff & hostPort.charAt(i));
+ switch (c2)
+ {
+ case ']':
+ break loop;
+
+ case ':':
+ try
+ {
+ len=i;
+ _port = StringUtil.toInt(hostPort.substring(i+1));
+ }
+ catch (NumberFormatException e)
+ {
+ LOG.warn(e);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
+ }
+ break loop;
+ }
+ }
+ if (hostPort.charAt(0)=='[')
+ {
+ if (hostPort.charAt(len-1)!=']')
+ {
+ LOG.warn("Bad IPv6 "+hostPort);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
+ }
+ _serverName = hostPort.substring(1,len-1);
+ }
+ else if (len==hostPort.length())
+ _serverName=hostPort;
+ else
+ _serverName = hostPort.substring(0,len);
+
+ return _serverName;
+ }
+
+ // Return host from connection
+ if (_channel != null)
+ {
+ _serverName = getLocalName();
+ _port = getLocalPort();
+ if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+ return _serverName;
+ }
+
+ // Return the local host
+ try
+ {
+ _serverName = InetAddress.getLocalHost().getHostAddress();
+ }
+ catch (java.net.UnknownHostException e)
+ {
+ LOG.ignore(e);
+ }
+ return _serverName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerPort()
+ */
+ @Override
+ public int getServerPort()
+ {
+ if (_port <= 0)
+ {
+ if (_serverName == null)
+ getServerName();
+
+ if (_port <= 0)
+ {
+ if (_serverName != null && _uri != null)
+ _port = _uri.getPort();
+ else
+ {
+ InetSocketAddress local = _channel.getLocalAddress();
+ _port = local == null?0:local.getPort();
+ }
+ }
+ }
+
+ if (_port <= 0)
+ {
+ if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+ return 443;
+ return 80;
+ }
+ return _port;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ServletContext getServletContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public String getServletName()
+ {
+ if (_scope != null)
+ return _scope.getName();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getServletPath()
+ */
+ @Override
+ public String getServletPath()
+ {
+ if (_servletPath == null)
+ _servletPath = "";
+ return _servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletResponse getServletResponse()
+ {
+ return _channel.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Add @override when 3.1 api is available
+ */
+ public String changeSessionId()
+ {
+ HttpSession session = getSession(false);
+ if (session == null)
+ throw new IllegalStateException("No session");
+
+ if (session instanceof AbstractSession)
+ {
+ AbstractSession abstractSession = ((AbstractSession)session);
+ abstractSession.renewId(this);
+ if (getRemoteUser() != null)
+ abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ if (abstractSession.isIdChanged())
+ _channel.getResponse().addCookie(_sessionManager.getSessionCookie(abstractSession, getContextPath(), isSecure()));
+ }
+
+ return session.getId();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession()
+ */
+ @Override
+ public HttpSession getSession()
+ {
+ return getSession(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+ */
+ @Override
+ public HttpSession getSession(boolean create)
+ {
+ if (_session != null)
+ {
+ if (_sessionManager != null && !_sessionManager.isValid(_session))
+ _session = null;
+ else
+ return _session;
+ }
+
+ if (!create)
+ return null;
+
+ if (getResponse().isCommitted())
+ throw new IllegalStateException("Response is committed");
+
+ if (_sessionManager == null)
+ throw new IllegalStateException("No SessionManager");
+
+ _session = _sessionManager.newHttpSession(this);
+ HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+ if (cookie != null)
+ _channel.getResponse().addCookie(cookie);
+
+ return _session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionManager.
+ */
+ public SessionManager getSessionManager()
+ {
+ return _sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request TimeStamp
+ *
+ * @return The time that the request was received.
+ */
+ public long getTimeStamp()
+ {
+ return _timeStamp;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the uri.
+ */
+ public HttpURI getUri()
+ {
+ return _uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getUserIdentity()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getUserIdentity();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
+ * {@link Authentication.Deferred}).
+ */
+ public UserIdentity getResolvedUserIdentity()
+ {
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getUserIdentity();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity.Scope getUserIdentityScope()
+ {
+ return _scope;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+ */
+ @Override
+ public Principal getUserPrincipal()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ {
+ UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
+ return user.getUserPrincipal();
+ }
+
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean isHandled()
+ {
+ return _handled;
+ }
+
+ @Override
+ public boolean isAsyncStarted()
+ {
+ return getHttpChannelState().isAsyncStarted();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isAsyncSupported()
+ {
+ return _asyncSupported;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ return _requestedSessionId != null && _requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromURL()
+ {
+ return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+ */
+ @Override
+ public boolean isRequestedSessionIdValid()
+ {
+ if (_requestedSessionId == null)
+ return false;
+
+ HttpSession session = getSession(false);
+ return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#isSecure()
+ */
+ @Override
+ public boolean isSecure()
+ {
+ return _secure;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSecure(boolean secure)
+ {
+ _secure=secure;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+ */
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).isUserInRole(_scope,role);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpSession recoverNewSession(Object key)
+ {
+ if (_savedNewSessions == null)
+ return null;
+ return _savedNewSessions.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void recycle()
+ {
+ if (_context != null)
+ throw new IllegalStateException("Request in context!");
+
+ if (_inputState == __READER)
+ {
+ try
+ {
+ int r = _reader.read();
+ while (r != -1)
+ r = _reader.read();
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ _reader = null;
+ }
+ }
+
+ _dispatcherType=null;
+ setAuthentication(Authentication.NOT_CHECKED);
+ getHttpChannelState().recycle();
+ if (_async!=null)
+ _async.reset();
+ _async=null;
+ _asyncSupported = true;
+ _handled = false;
+ if (_attributes != null)
+ _attributes.clearAttributes();
+ _characterEncoding = null;
+ _contextPath = null;
+ if (_cookies != null)
+ _cookies.reset();
+ _cookiesExtracted = false;
+ _context = null;
+ _newContext=false;
+ _serverName = null;
+ _httpMethod=null;
+ _httpMethodString = null;
+ _pathInfo = null;
+ _port = 0;
+ _httpVersion = HttpVersion.HTTP_1_1;
+ _queryEncoding = null;
+ _queryString = null;
+ _requestedSessionId = null;
+ _requestedSessionIdFromCookie = false;
+ _secure=false;
+ _session = null;
+ _sessionManager = null;
+ _requestURI = null;
+ _scope = null;
+ _scheme = URIUtil.HTTP;
+ _servletPath = null;
+ _timeStamp = 0;
+ _uri = null;
+ _queryParameters = null;
+ _contentParameters = null;
+ _parameters = null;
+ _paramsExtracted = false;
+ _inputState = __NONE;
+
+ if (_savedNewSessions != null)
+ _savedNewSessions.clear();
+ _savedNewSessions=null;
+ _multiPartInputStream = null;
+ _remote=null;
+ _fields.clear();
+ _input.recycle();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+ */
+ @Override
+ public void removeAttribute(String name)
+ {
+ Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+ if (_attributes != null)
+ _attributes.removeAttribute(name);
+
+ if (old_value != null && !_requestAttributeListeners.isEmpty())
+ {
+ final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+ for (ServletRequestAttributeListener listener : _requestAttributeListeners)
+ listener.attributeRemoved(event);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeEventListener(final EventListener listener)
+ {
+ _requestAttributeListeners.remove(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void saveNewSession(Object key, HttpSession session)
+ {
+ if (_savedNewSessions == null)
+ _savedNewSessions = new HashMap<>();
+ _savedNewSessions.put(key,session);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAsyncSupported(boolean supported)
+ {
+ _asyncSupported = supported;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
+ * {@link #setQueryEncoding}.
+ *
+ * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void setAttribute(String name, Object value)
+ {
+ Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+ if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+ setQueryEncoding(value == null?null:value.toString());
+ else if ("org.eclipse.jetty.server.sendContent".equals(name))
+ LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+
+ if (_attributes == null)
+ _attributes = new AttributesMap();
+ _attributes.setAttribute(name,value);
+
+ if (!_requestAttributeListeners.isEmpty())
+ {
+ final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
+ for (ServletRequestAttributeListener l : _requestAttributeListeners)
+ {
+ if (old_value == null)
+ l.attributeAdded(event);
+ else if (value == null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public void setAttributes(Attributes attributes)
+ {
+ _attributes = attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the authentication.
+ *
+ * @param authentication
+ * the authentication to set
+ */
+ public void setAuthentication(Authentication authentication)
+ {
+ _authentication = authentication;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ @Override
+ public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+ {
+ if (_inputState != __NONE)
+ return;
+
+ _characterEncoding = encoding;
+
+ // check encoding is supported
+ if (!StringUtil.isUTF8(encoding))
+ {
+ try
+ {
+ Charset.forName(encoding);
+ }
+ catch (UnsupportedCharsetException e)
+ {
+ throw new UnsupportedEncodingException(e.getMessage());
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ public void setCharacterEncodingUnchecked(String encoding)
+ {
+ _characterEncoding = encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ public void setContentType(String contentType)
+ {
+ _fields.put(HttpHeader.CONTENT_TYPE,contentType);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set request context
+ *
+ * @param context
+ * context object
+ */
+ public void setContext(Context context)
+ {
+ _newContext = _context != context;
+ _context = context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if this is the first call of {@link #takeNewContext()} since the last
+ * {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
+ */
+ public boolean takeNewContext()
+ {
+ boolean nc = _newContext;
+ _newContext = false;
+ return nc;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the "context path" for this request
+ *
+ * @see HttpServletRequest#getContextPath()
+ */
+ public void setContextPath(String contextPath)
+ {
+ _contextPath = contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param cookies
+ * The cookies to set.
+ */
+ public void setCookies(Cookie[] cookies)
+ {
+ if (_cookies == null)
+ _cookies = new CookieCutter();
+ _cookies.setCookies(cookies);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherType(DispatcherType type)
+ {
+ _dispatcherType = type;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setHandled(boolean h)
+ {
+ _handled = h;
+ Response r=getResponse();
+ if (_handled && r.getStatus()==0)
+ r.setStatus(200);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param method
+ * The method to set.
+ */
+ public void setMethod(HttpMethod httpMethod, String method)
+ {
+ _httpMethod=httpMethod;
+ _httpMethodString = method;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isHead()
+ {
+ return HttpMethod.HEAD==_httpMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathInfo
+ * The pathInfo to set.
+ */
+ public void setPathInfo(String pathInfo)
+ {
+ _pathInfo = pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param version
+ * The protocol to set.
+ */
+ public void setHttpVersion(HttpVersion version)
+ {
+ _httpVersion = version;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
+ * getParameter methods.
+ *
+ * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
+ *
+ * @param queryEncoding
+ */
+ public void setQueryEncoding(String queryEncoding)
+ {
+ _queryEncoding = queryEncoding;
+ _queryString = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param queryString
+ * The queryString to set.
+ */
+ public void setQueryString(String queryString)
+ {
+ _queryString = queryString;
+ _queryEncoding = null; //assume utf-8
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param addr
+ * The address to set.
+ */
+ public void setRemoteAddr(InetSocketAddress addr)
+ {
+ _remote = addr;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionId
+ * The requestedSessionId to set.
+ */
+ public void setRequestedSessionId(String requestedSessionId)
+ {
+ _requestedSessionId = requestedSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionIdCookie
+ * The requestedSessionIdCookie to set.
+ */
+ public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+ {
+ _requestedSessionIdFromCookie = requestedSessionIdCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestURI
+ * The requestURI to set.
+ */
+ public void setRequestURI(String requestURI)
+ {
+ _requestURI = requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param scheme
+ * The scheme to set.
+ */
+ public void setScheme(String scheme)
+ {
+ _scheme = scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param host
+ * The host to set.
+ */
+ public void setServerName(String host)
+ {
+ _serverName = host;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param port
+ * The port to set.
+ */
+ public void setServerPort(int port)
+ {
+ _port = port;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletPath
+ * The servletPath to set.
+ */
+ public void setServletPath(String servletPath)
+ {
+ _servletPath = servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session
+ * The session to set.
+ */
+ public void setSession(HttpSession session)
+ {
+ _session = session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionManager
+ * The sessionManager to set.
+ */
+ public void setSessionManager(SessionManager sessionManager)
+ {
+ _sessionManager = sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setTimeStamp(long ts)
+ {
+ _timeStamp = ts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param uri
+ * The uri to set.
+ */
+ public void setUri(HttpURI uri)
+ {
+ _uri = uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setUserIdentityScope(UserIdentity.Scope scope)
+ {
+ _scope = scope;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+ state.startAsync(event);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+ event.setDispatchContext(getServletContext());
+ event.setDispatchPath(URIUtil.addPaths(getServletPath(),getPathInfo()));
+ state.startAsync(event);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ {
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
+ return !(_authentication instanceof Authentication.ResponseSent);
+ }
+ response.sendError(HttpStatus.UNAUTHORIZED_401);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Part getPart(String name) throws IOException, ServletException
+ {
+ getParts();
+
+ return _multiPartInputStream.getPart(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Collection getParts() throws IOException, ServletException
+ {
+ if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
+ throw new ServletException("Content-Type != multipart/form-data");
+ return getParts(null);
+ }
+
+ private Collection getParts(MultiMap params) throws IOException, ServletException
+ {
+ if (_multiPartInputStream == null)
+ _multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
+
+ if (_multiPartInputStream == null)
+ {
+ MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
+
+ if (config == null)
+ throw new IllegalStateException("No multipart config for servlet");
+
+ _multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
+ getContentType(), config,
+ (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
+
+ setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
+ setAttribute(__MULTIPART_CONTEXT, _context);
+ Collection parts = _multiPartInputStream.getParts(); //causes parsing
+ ByteArrayOutputStream os = null;
+ for (Part p:parts)
+ {
+ MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
+ if (mp.getContentDispositionFilename() == null)
+ {
+ // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+ String charset = null;
+ if (mp.getContentType() != null)
+ charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
+
+ try (InputStream is = mp.getInputStream())
+ {
+ if (os == null)
+ os = new ByteArrayOutputStream();
+ IO.copy(is, os);
+ String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
+ if (_contentParameters == null)
+ _contentParameters = params == null ? new MultiMap() : params;
+ _contentParameters.add(mp.getName(), content);
+ }
+ os.reset();
+ }
+ }
+ }
+
+ return _multiPartInputStream.getParts();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void login(String username, String password) throws ServletException
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ {
+ _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
+ if (_authentication == null)
+ throw new Authentication.Failed("Authentication failed for username '"+username+"'");
+ }
+ else
+ {
+ throw new Authentication.Failed("Authenticated failed for username '"+username+"'. Already authenticated as "+_authentication);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void logout() throws ServletException
+ {
+ if (_authentication instanceof Authentication.User)
+ ((Authentication.User)_authentication).logout();
+ _authentication=Authentication.UNAUTHENTICATED;
+ }
+
+ public void mergeQueryParameters(String newQuery, boolean updateQueryString)
+ {
+ MultiMap newQueryParams = new MultiMap<>();
+ // Have to assume ENCODING because we can't know otherwise.
+ UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
+
+ MultiMap