diff --git a/.gems/cache/addressable-2.3.6.gem b/.gems/cache/addressable-2.3.6.gem new file mode 100644 index 0000000..254dec8 Binary files /dev/null and b/.gems/cache/addressable-2.3.6.gem differ diff --git a/.gems/cache/buftok-0.2.0.gem b/.gems/cache/buftok-0.2.0.gem new file mode 100644 index 0000000..2c3d94b Binary files /dev/null and b/.gems/cache/buftok-0.2.0.gem differ diff --git a/.gems/cache/equalizer-0.0.9.gem b/.gems/cache/equalizer-0.0.9.gem new file mode 100644 index 0000000..2072c17 Binary files /dev/null and b/.gems/cache/equalizer-0.0.9.gem differ diff --git a/.gems/cache/faraday-0.9.0.gem b/.gems/cache/faraday-0.9.0.gem new file mode 100644 index 0000000..0b00e52 Binary files /dev/null and b/.gems/cache/faraday-0.9.0.gem differ diff --git a/.gems/cache/http-0.6.2.gem b/.gems/cache/http-0.6.2.gem new file mode 100644 index 0000000..00fc84b Binary files /dev/null and b/.gems/cache/http-0.6.2.gem differ diff --git a/.gems/cache/http_parser.rb-0.6.0.gem b/.gems/cache/http_parser.rb-0.6.0.gem new file mode 100644 index 0000000..3d3d508 Binary files /dev/null and b/.gems/cache/http_parser.rb-0.6.0.gem differ diff --git a/.gems/cache/memoizable-0.4.2.gem b/.gems/cache/memoizable-0.4.2.gem new file mode 100644 index 0000000..41d1f5b Binary files /dev/null and b/.gems/cache/memoizable-0.4.2.gem differ diff --git a/.gems/cache/multipart-post-2.0.0.gem b/.gems/cache/multipart-post-2.0.0.gem new file mode 100644 index 0000000..abfff3d Binary files /dev/null and b/.gems/cache/multipart-post-2.0.0.gem differ diff --git a/.gems/cache/naught-1.0.0.gem b/.gems/cache/naught-1.0.0.gem new file mode 100644 index 0000000..f59502d Binary files /dev/null and b/.gems/cache/naught-1.0.0.gem differ diff --git a/.gems/cache/simple_oauth-0.2.0.gem b/.gems/cache/simple_oauth-0.2.0.gem new file mode 100644 index 0000000..5901361 Binary files /dev/null and b/.gems/cache/simple_oauth-0.2.0.gem differ diff --git a/.gems/cache/thread_safe-0.3.4.gem b/.gems/cache/thread_safe-0.3.4.gem new file mode 100644 index 0000000..5bf4376 Binary files /dev/null and b/.gems/cache/thread_safe-0.3.4.gem differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBadInput/cdesc-PunycodeBadInput.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBadInput/cdesc-PunycodeBadInput.ri new file mode 100644 index 0000000..dee133c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBadInput/cdesc-PunycodeBadInput.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBigOutput/cdesc-PunycodeBigOutput.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBigOutput/cdesc-PunycodeBigOutput.ri new file mode 100644 index 0000000..82d722b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeBigOutput/cdesc-PunycodeBigOutput.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeOverflow/cdesc-PunycodeOverflow.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeOverflow/cdesc-PunycodeOverflow.ri new file mode 100644 index 0000000..3f18a58 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/PunycodeOverflow/cdesc-PunycodeOverflow.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/cdesc-IDNA.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/cdesc-IDNA.ri new file mode 100644 index 0000000..2102b4a Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/cdesc-IDNA.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_combining_class-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_combining_class-c.ri new file mode 100644 index 0000000..927c702 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_combining_class-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_compatibility-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_compatibility-c.ri new file mode 100644 index 0000000..a4808fd Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_compatibility-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_composition-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_composition-c.ri new file mode 100644 index 0000000..8dad303 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_composition-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_lowercase-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_lowercase-c.ri new file mode 100644 index 0000000..f9b3d13 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/lookup_unicode_lowercase-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_adapt-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_adapt-c.ri new file mode 100644 index 0000000..50e1610 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_adapt-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_basic%3f-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_basic%3f-c.ri new file mode 100644 index 0000000..0df5fca Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_basic%3f-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode-c.ri new file mode 100644 index 0000000..28379a1 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode_digit-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode_digit-c.ri new file mode 100644 index 0000000..c452569 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_decode_digit-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_delimiter%3f-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_delimiter%3f-c.ri new file mode 100644 index 0000000..6011851 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_delimiter%3f-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode-c.ri new file mode 100644 index 0000000..b596bb4 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode_digit-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode_digit-c.ri new file mode 100644 index 0000000..41d6b8e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/punycode_encode_digit-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_ascii-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_ascii-c.ri new file mode 100644 index 0000000..f268c8d Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_ascii-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_unicode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_unicode-c.ri new file mode 100644 index 0000000..f523f1e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/to_unicode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose-c.ri new file mode 100644 index 0000000..f8f99e7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose_pair-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose_pair-c.ri new file mode 100644 index 0000000..f58ea7b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_compose_pair-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose-c.ri new file mode 100644 index 0000000..1f8caca Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose_hangul-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose_hangul-c.ri new file mode 100644 index 0000000..bc8de90 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_decompose_hangul-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_downcase-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_downcase-c.ri new file mode 100644 index 0000000..9e8d0d8 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_downcase-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_normalize_kc-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_normalize_kc-c.ri new file mode 100644 index 0000000..ef9a4d5 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_normalize_kc-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_sort_canonical-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_sort_canonical-c.ri new file mode 100644 index 0000000..92f7823 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/IDNA/unicode_sort_canonical-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/%3d%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/%3d%3d-i.ri new file mode 100644 index 0000000..f6901e8 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/%3d%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateOperatorError/cdesc-InvalidTemplateOperatorError.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateOperatorError/cdesc-InvalidTemplateOperatorError.ri new file mode 100644 index 0000000..f69f181 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateOperatorError/cdesc-InvalidTemplateOperatorError.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateValueError/cdesc-InvalidTemplateValueError.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateValueError/cdesc-InvalidTemplateValueError.ri new file mode 100644 index 0000000..a01b4ff Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/InvalidTemplateValueError/cdesc-InvalidTemplateValueError.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/%5b%5d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/%5b%5d-i.ri new file mode 100644 index 0000000..19bafd7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/%5b%5d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/captures-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/captures-i.ri new file mode 100644 index 0000000..c5cac22 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/captures-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/cdesc-MatchData.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/cdesc-MatchData.ri new file mode 100644 index 0000000..27bd0c0 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/cdesc-MatchData.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/inspect-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/inspect-i.ri new file mode 100644 index 0000000..02a4b07 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/inspect-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/keys-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/keys-i.ri new file mode 100644 index 0000000..58ed844 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/keys-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/mapping-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/mapping-i.ri new file mode 100644 index 0000000..59ae8e9 --- /dev/null +++ b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/mapping-i.ri @@ -0,0 +1,5 @@ +U:RDoc::Attr[iI" mapping:ETI"-Addressable::Template::MatchData#mapping;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"@return [Hash];To:RDoc::Markup::Verbatim; [ I"/The mapping that resulted from the match. +;TI"@Note that this mapping does not include keys or values for +;TI"@variables that appear in the Template, but are not present +;TI"in the URI.;T: @format0: +@fileI" lib/addressable/template.rb;T:0@omit_headings_from_table_of_contents_below0F@I"%Addressable::Template::MatchData;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/names-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/names-i.ri new file mode 100644 index 0000000..9cbdceb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/names-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/new-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/new-c.ri new file mode 100644 index 0000000..b7eb12e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/new-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/post_match-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/post_match-i.ri new file mode 100644 index 0000000..6068a70 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/post_match-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/pre_match-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/pre_match-i.ri new file mode 100644 index 0000000..300d305 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/pre_match-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/string-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/string-i.ri new file mode 100644 index 0000000..448aed0 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/string-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/template-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/template-i.ri new file mode 100644 index 0000000..d59941e --- /dev/null +++ b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/template-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" template:ETI".Addressable::Template::MatchData#template;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"$@return [Addressable::Template];To:RDoc::Markup::Verbatim; [I"%The Template used for the match.;T: @format0: +@fileI" lib/addressable/template.rb;T:0@omit_headings_from_table_of_contents_below0F@I"%Addressable::Template::MatchData;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_a-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_a-i.ri new file mode 100644 index 0000000..f933571 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_a-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_s-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_s-i.ri new file mode 100644 index 0000000..a447681 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/to_s-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/uri-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/uri-i.ri new file mode 100644 index 0000000..876e82e --- /dev/null +++ b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/uri-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"uri:ETI")Addressable::Template::MatchData#uri;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"@return [Addressable::URI];To:RDoc::Markup::Verbatim; [I"3The URI that the Template was matched against.;T: @format0: +@fileI" lib/addressable/template.rb;T:0@omit_headings_from_table_of_contents_below0F@I"%Addressable::Template::MatchData;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values-i.ri new file mode 100644 index 0000000..2303ee9 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values_at-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values_at-i.ri new file mode 100644 index 0000000..b1613bf Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/values_at-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/variables-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/variables-i.ri new file mode 100644 index 0000000..d401b80 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/MatchData/variables-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/TemplateOperatorAbortedError/cdesc-TemplateOperatorAbortedError.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/TemplateOperatorAbortedError/cdesc-TemplateOperatorAbortedError.ri new file mode 100644 index 0000000..b493c35 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/TemplateOperatorAbortedError/cdesc-TemplateOperatorAbortedError.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/cdesc-Template.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/cdesc-Template.ri new file mode 100644 index 0000000..42653fb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/cdesc-Template.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/eql%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/eql%3f-i.ri new file mode 100644 index 0000000..3d58f0c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/eql%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/expand-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/expand-i.ri new file mode 100644 index 0000000..18b9cd9 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/expand-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/extract-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/extract-i.ri new file mode 100644 index 0000000..93a14b8 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/extract-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/inspect-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/inspect-i.ri new file mode 100644 index 0000000..2841bd2 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/inspect-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/join_values-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/join_values-i.ri new file mode 100644 index 0000000..7f3ab62 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/join_values-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/keys-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/keys-i.ri new file mode 100644 index 0000000..326a234 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/keys-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/match-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/match-i.ri new file mode 100644 index 0000000..229b4ee Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/match-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/new-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/new-c.ri new file mode 100644 index 0000000..fd45eb8 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/new-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_keys-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_keys-i.ri new file mode 100644 index 0000000..f40a56a Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_keys-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_value-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_value-i.ri new file mode 100644 index 0000000..d6984cf Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/normalize_value-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/ordered_variable_defaults-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/ordered_variable_defaults-i.ri new file mode 100644 index 0000000..82f0d8e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/ordered_variable_defaults-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/parse_template_pattern-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/parse_template_pattern-i.ri new file mode 100644 index 0000000..951406f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/parse_template_pattern-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/partial_expand-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/partial_expand-i.ri new file mode 100644 index 0000000..a39f377 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/partial_expand-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/pattern-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/pattern-i.ri new file mode 100644 index 0000000..7f529be --- /dev/null +++ b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/pattern-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" pattern:ETI""Addressable::Template#pattern;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"4@return [String] The Template object's pattern.;T: +@fileI" lib/addressable/template.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Addressable::Template;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_capture-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_capture-i.ri new file mode 100644 index 0000000..263607f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_capture-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_partial_capture-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_partial_capture-i.ri new file mode 100644 index 0000000..7e01533 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/transform_partial_capture-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variable_defaults-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variable_defaults-i.ri new file mode 100644 index 0000000..d3288fe Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variable_defaults-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variables-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variables-i.ri new file mode 100644 index 0000000..1feb7ac Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/Template/variables-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%2b-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%2b-i.ri new file mode 100644 index 0000000..8515363 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%2b-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d%3d-i.ri new file mode 100644 index 0000000..f496cda Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d-i.ri new file mode 100644 index 0000000..dcfd8b0 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/%3d%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/CharacterClasses/cdesc-CharacterClasses.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/CharacterClasses/cdesc-CharacterClasses.ri new file mode 100644 index 0000000..de32c46 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/CharacterClasses/cdesc-CharacterClasses.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/InvalidURIError/cdesc-InvalidURIError.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/InvalidURIError/cdesc-InvalidURIError.ri new file mode 100644 index 0000000..baeb79f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/InvalidURIError/cdesc-InvalidURIError.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/absolute%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/absolute%3f-i.ri new file mode 100644 index 0000000..712754c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/absolute%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority%3d-i.ri new file mode 100644 index 0000000..64e717f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority-i.ri new file mode 100644 index 0000000..bc18f42 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/authority-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/basename-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/basename-i.ri new file mode 100644 index 0000000..e9e5a90 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/basename-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/cdesc-URI.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/cdesc-URI.ri new file mode 100644 index 0000000..b2cffa3 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/cdesc-URI.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/convert_path-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/convert_path-c.ri new file mode 100644 index 0000000..3ec47bb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/convert_path-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/default_port-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/default_port-i.ri new file mode 100644 index 0000000..83873ac Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/default_port-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/defer_validation-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/defer_validation-i.ri new file mode 100644 index 0000000..55b4b77 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/defer_validation-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/display_uri-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/display_uri-i.ri new file mode 100644 index 0000000..b42c0fd Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/display_uri-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/dup-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/dup-i.ri new file mode 100644 index 0000000..5c39326 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/dup-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/empty%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/empty%3f-i.ri new file mode 100644 index 0000000..2fe9721 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/empty%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode-c.ri new file mode 100644 index 0000000..21a41e6 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode_component-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode_component-c.ri new file mode 100644 index 0000000..13d93ea Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/encode_component-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/eql%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/eql%3f-i.ri new file mode 100644 index 0000000..5b258dc Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/eql%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/escape-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/escape-c.ri new file mode 100644 index 0000000..18b0eb2 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/escape-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/extname-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/extname-i.ri new file mode 100644 index 0000000..ceb403e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/extname-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_encode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_encode-c.ri new file mode 100644 index 0000000..1e39ac7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_encode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_unencode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_unencode-c.ri new file mode 100644 index 0000000..c08a409 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/form_unencode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment%3d-i.ri new file mode 100644 index 0000000..8548a03 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment-i.ri new file mode 100644 index 0000000..31c685c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/fragment-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/freeze-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/freeze-i.ri new file mode 100644 index 0000000..2ea9762 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/freeze-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hash-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hash-i.ri new file mode 100644 index 0000000..6072bce Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hash-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/heuristic_parse-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/heuristic_parse-c.ri new file mode 100644 index 0000000..39dfc28 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/heuristic_parse-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host%3d-i.ri new file mode 100644 index 0000000..adb8181 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host-i.ri new file mode 100644 index 0000000..acb1bb7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/host-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname%3d-i.ri new file mode 100644 index 0000000..30c153e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname-i.ri new file mode 100644 index 0000000..6cc3c10 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/hostname-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inferred_port-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inferred_port-i.ri new file mode 100644 index 0000000..b67d6c3 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inferred_port-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inspect-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inspect-i.ri new file mode 100644 index 0000000..6f30faf Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/inspect-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based%3f-i.ri new file mode 100644 index 0000000..e0b8571 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based_schemes-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based_schemes-c.ri new file mode 100644 index 0000000..28d22bb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/ip_based_schemes-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join%21-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join%21-i.ri new file mode 100644 index 0000000..53b83e4 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join%21-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-c.ri new file mode 100644 index 0000000..f344b72 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-i.ri new file mode 100644 index 0000000..186a5b7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/join-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge%21-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge%21-i.ri new file mode 100644 index 0000000..3c1be3b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge%21-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge-i.ri new file mode 100644 index 0000000..7bac382 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/merge-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/new-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/new-c.ri new file mode 100644 index 0000000..4d4b935 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/new-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize%21-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize%21-i.ri new file mode 100644 index 0000000..373750e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize%21-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize-i.ri new file mode 100644 index 0000000..5aea3c7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_component-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_component-c.ri new file mode 100644 index 0000000..456c69b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_component-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_path-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_path-c.ri new file mode 100644 index 0000000..af3f998 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalize_path-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_authority-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_authority-i.ri new file mode 100644 index 0000000..cf42d8d Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_authority-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_encode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_encode-c.ri new file mode 100644 index 0000000..262d11b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_encode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_fragment-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_fragment-i.ri new file mode 100644 index 0000000..efda11e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_fragment-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_host-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_host-i.ri new file mode 100644 index 0000000..0992c09 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_host-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_password-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_password-i.ri new file mode 100644 index 0000000..908a640 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_password-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_path-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_path-i.ri new file mode 100644 index 0000000..d57320a Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_path-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_port-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_port-i.ri new file mode 100644 index 0000000..97b2f61 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_port-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_query-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_query-i.ri new file mode 100644 index 0000000..c879a7f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_query-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_scheme-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_scheme-i.ri new file mode 100644 index 0000000..2a98d72 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_scheme-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_site-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_site-i.ri new file mode 100644 index 0000000..b3fd004 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_site-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_user-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_user-i.ri new file mode 100644 index 0000000..ab7900f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_user-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_userinfo-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_userinfo-i.ri new file mode 100644 index 0000000..683204e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/normalized_userinfo-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit%21-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit%21-i.ri new file mode 100644 index 0000000..06c0140 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit%21-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit-i.ri new file mode 100644 index 0000000..3a2e5f0 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/omit-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/origin-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/origin-i.ri new file mode 100644 index 0000000..b57be0c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/origin-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/parse-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/parse-c.ri new file mode 100644 index 0000000..77bbef8 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/parse-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password%3d-i.ri new file mode 100644 index 0000000..ac4fea2 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password-i.ri new file mode 100644 index 0000000..f1ed73b Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/password-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path%3d-i.ri new file mode 100644 index 0000000..e362625 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path-i.ri new file mode 100644 index 0000000..07ffcc3 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/path-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port%3d-i.ri new file mode 100644 index 0000000..23ced03 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port-i.ri new file mode 100644 index 0000000..9b7455e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port_mapping-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port_mapping-c.ri new file mode 100644 index 0000000..3110575 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/port_mapping-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query%3d-i.ri new file mode 100644 index 0000000..b843a29 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query-i.ri new file mode 100644 index 0000000..a5c5cd9 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values%3d-i.ri new file mode 100644 index 0000000..f596b54 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values-i.ri new file mode 100644 index 0000000..b4d86fb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/query_values-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/relative%3f-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/relative%3f-i.ri new file mode 100644 index 0000000..362bb38 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/relative%3f-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/replace_self-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/replace_self-i.ri new file mode 100644 index 0000000..f4e1d8c Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/replace_self-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri%3d-i.ri new file mode 100644 index 0000000..cce22b3 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri-i.ri new file mode 100644 index 0000000..53a8fba Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/request_uri-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_from-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_from-i.ri new file mode 100644 index 0000000..b7a8be9 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_from-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_to-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_to-i.ri new file mode 100644 index 0000000..9935b81 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/route_to-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme%3d-i.ri new file mode 100644 index 0000000..ac510dd Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme-i.ri new file mode 100644 index 0000000..c5ceabc Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/scheme-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site%3d-i.ri new file mode 100644 index 0000000..cad8bef Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site-i.ri new file mode 100644 index 0000000..dbe0de0 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/site-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/split_path-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/split_path-i.ri new file mode 100644 index 0000000..df967e9 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/split_path-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_hash-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_hash-i.ri new file mode 100644 index 0000000..c35afab Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_hash-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_s-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_s-i.ri new file mode 100644 index 0000000..d1601eb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_s-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_str-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_str-i.ri new file mode 100644 index 0000000..a615890 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/to_str-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode-c.ri new file mode 100644 index 0000000..8a4594a Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode_component-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode_component-c.ri new file mode 100644 index 0000000..03dab1e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unencode_component-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape-c.ri new file mode 100644 index 0000000..3305f8d Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape_component-c.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape_component-c.ri new file mode 100644 index 0000000..ed20fc7 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/unescape_component-c.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user%3d-i.ri new file mode 100644 index 0000000..cd7a0f5 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user-i.ri new file mode 100644 index 0000000..68331d1 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/user-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo%3d-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo%3d-i.ri new file mode 100644 index 0000000..6d7318e Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo%3d-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo-i.ri new file mode 100644 index 0000000..d114082 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/userinfo-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/URI/validate-i.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/validate-i.ri new file mode 100644 index 0000000..14b772f Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/URI/validate-i.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/VERSION/cdesc-VERSION.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/VERSION/cdesc-VERSION.ri new file mode 100644 index 0000000..ca46ea3 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/VERSION/cdesc-VERSION.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/Addressable/cdesc-Addressable.ri b/.gems/doc/addressable-2.3.6/ri/Addressable/cdesc-Addressable.ri new file mode 100644 index 0000000..12e25ad Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/Addressable/cdesc-Addressable.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/cache.ri b/.gems/doc/addressable-2.3.6/ri/cache.ri new file mode 100644 index 0000000..6e2de72 Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/cache.ri differ diff --git a/.gems/doc/addressable-2.3.6/ri/page-README_md.ri b/.gems/doc/addressable-2.3.6/ri/page-README_md.ri new file mode 100644 index 0000000..ea840eb Binary files /dev/null and b/.gems/doc/addressable-2.3.6/ri/page-README_md.ri differ diff --git a/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/cdesc-BufferedTokenizer.ri b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/cdesc-BufferedTokenizer.ri new file mode 100644 index 0000000..cba49a3 Binary files /dev/null and b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/cdesc-BufferedTokenizer.ri differ diff --git a/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/extract-i.ri b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/extract-i.ri new file mode 100644 index 0000000..47ac9d3 Binary files /dev/null and b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/extract-i.ri differ diff --git a/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/flush-i.ri b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/flush-i.ri new file mode 100644 index 0000000..595d373 Binary files /dev/null and b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/flush-i.ri differ diff --git a/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/new-c.ri b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/new-c.ri new file mode 100644 index 0000000..9bc4ec8 Binary files /dev/null and b/.gems/doc/buftok-0.2.0/ri/BufferedTokenizer/new-c.ri differ diff --git a/.gems/doc/buftok-0.2.0/ri/cache.ri b/.gems/doc/buftok-0.2.0/ri/cache.ri new file mode 100644 index 0000000..ee107e3 Binary files /dev/null and b/.gems/doc/buftok-0.2.0/ri/cache.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/%3d%3d-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/%3d%3d-i.ri new file mode 100644 index 0000000..33327fa Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/%3d%3d-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/cdesc-Methods.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/cdesc-Methods.ri new file mode 100644 index 0000000..373e4f2 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/cdesc-Methods.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/eql%3f-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/eql%3f-i.ri new file mode 100644 index 0000000..9526048 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/Methods/eql%3f-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/cdesc-Equalizer.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/cdesc-Equalizer.ri new file mode 100644 index 0000000..eebb130 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/cdesc-Equalizer.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_cmp_method-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_cmp_method-i.ri new file mode 100644 index 0000000..eeb6df8 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_cmp_method-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_hash_method-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_hash_method-i.ri new file mode 100644 index 0000000..b6ec3c6 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_hash_method-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_inspect_method-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_inspect_method-i.ri new file mode 100644 index 0000000..b0d4032 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_inspect_method-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_methods-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_methods-i.ri new file mode 100644 index 0000000..02ad398 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/define_methods-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/included-i.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/included-i.ri new file mode 100644 index 0000000..614d482 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/included-i.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/Equalizer/new-c.ri b/.gems/doc/equalizer-0.0.9/ri/Equalizer/new-c.ri new file mode 100644 index 0000000..fb378fa Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/Equalizer/new-c.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/cache.ri b/.gems/doc/equalizer-0.0.9/ri/cache.ri new file mode 100644 index 0000000..ce6290c Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/cache.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/page-CONTRIBUTING_md.ri b/.gems/doc/equalizer-0.0.9/ri/page-CONTRIBUTING_md.ri new file mode 100644 index 0000000..e8109e3 Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/page-CONTRIBUTING_md.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/page-LICENSE.ri b/.gems/doc/equalizer-0.0.9/ri/page-LICENSE.ri new file mode 100644 index 0000000..d2bd28d Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/page-LICENSE.ri differ diff --git a/.gems/doc/equalizer-0.0.9/ri/page-README_md.ri b/.gems/doc/equalizer-0.0.9/ri/page-README_md.ri new file mode 100644 index 0000000..43ad3cf Binary files /dev/null and b/.gems/doc/equalizer-0.0.9/ri/page-README_md.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/cdesc-EmHttpSslPatch.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/cdesc-EmHttpSslPatch.ri new file mode 100644 index 0000000..8c415d8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/cdesc-EmHttpSslPatch.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/certificate_store-i.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/certificate_store-i.ri new file mode 100644 index 0000000..fd6d7f4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/certificate_store-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/host-i.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/host-i.ri new file mode 100644 index 0000000..5ae0a8e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/host-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_handshake_completed-i.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_handshake_completed-i.ri new file mode 100644 index 0000000..dba5a70 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_handshake_completed-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_verify_peer-i.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_verify_peer-i.ri new file mode 100644 index 0000000..f63e43f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/ssl_verify_peer-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/verify_peer%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/verify_peer%3f-i.ri new file mode 100644 index 0000000..2fd8d73 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EmHttpSslPatch/verify_peer%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/EventMachine/cdesc-EventMachine.ri b/.gems/doc/faraday-0.9.0/ri/EventMachine/cdesc-EventMachine.ri new file mode 100644 index 0000000..7332911 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/EventMachine/cdesc-EventMachine.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/add-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/add-i.ri new file mode 100644 index 0000000..64d8567 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/add-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/cdesc-Manager.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/cdesc-Manager.ri new file mode 100644 index 0000000..86fb211 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/cdesc-Manager.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/check_finished-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/check_finished-i.ri new file mode 100644 index 0000000..537a2da Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/check_finished-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/new-c.ri new file mode 100644 index 0000000..07ae3cc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/perform_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/perform_request-i.ri new file mode 100644 index 0000000..276bfb5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/perform_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/reset-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/reset-i.ri new file mode 100644 index 0000000..d67ba02 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/reset-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/run-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/run-i.ri new file mode 100644 index 0000000..bf09257 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/run-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/running%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/running%3f-i.ri new file mode 100644 index 0000000..c8a0274 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Manager/running%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/cdesc-Options.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/cdesc-Options.ri new file mode 100644 index 0000000..d93455f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/cdesc-Options.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_compression-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_compression-i.ri new file mode 100644 index 0000000..5bc562e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_compression-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_proxy-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_proxy-i.ri new file mode 100644 index 0000000..2079536 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_proxy-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_socket-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_socket-i.ri new file mode 100644 index 0000000..a147bae Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_socket-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_ssl-i.ri new file mode 100644 index 0000000..c780840 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_ssl-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_timeout-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_timeout-i.ri new file mode 100644 index 0000000..aeeab94 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/configure_timeout-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/connection_config-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/connection_config-i.ri new file mode 100644 index 0000000..e25cf17 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/connection_config-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/read_body-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/read_body-i.ri new file mode 100644 index 0000000..5f3088a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/read_body-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_config-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_config-i.ri new file mode 100644 index 0000000..e28d64a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_config-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_options-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_options-i.ri new file mode 100644 index 0000000..a738e16 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/Options/request_options-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/call-i.ri new file mode 100644 index 0000000..5dfab40 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/cdesc-EMHttp.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/cdesc-EMHttp.ri new file mode 100644 index 0000000..a5c41b3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/cdesc-EMHttp.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/error_message-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/error_message-i.ri new file mode 100644 index 0000000..a55b376 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/error_message-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/parallel%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/parallel%3f-i.ri new file mode 100644 index 0000000..15cd9d1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/parallel%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_request-i.ri new file mode 100644 index 0000000..cb3e18a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_single_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_single_request-i.ri new file mode 100644 index 0000000..fd4f986 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/perform_single_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/raise_error-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/raise_error-i.ri new file mode 100644 index 0000000..4814dcd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/raise_error-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/setup_parallel_manager-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/setup_parallel_manager-c.ri new file mode 100644 index 0000000..4101c59 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMHttp/setup_parallel_manager-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/add-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/add-i.ri new file mode 100644 index 0000000..68d51ba Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/add-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/cdesc-ParallelManager.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/cdesc-ParallelManager.ri new file mode 100644 index 0000000..65f8ac4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/cdesc-ParallelManager.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/perform-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/perform-i.ri new file mode 100644 index 0000000..47aeb23 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/perform-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/queue-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/queue-i.ri new file mode 100644 index 0000000..eebc23e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/queue-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/run-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/run-i.ri new file mode 100644 index 0000000..66d1715 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/ParallelManager/run-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/call-i.ri new file mode 100644 index 0000000..2018fc6 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/cdesc-EMSynchrony.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/cdesc-EMSynchrony.ri new file mode 100644 index 0000000..8ebbf02 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/cdesc-EMSynchrony.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/setup_parallel_manager-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/setup_parallel_manager-c.ri new file mode 100644 index 0000000..8c54a49 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/EMSynchrony/setup_parallel_manager-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/call-i.ri new file mode 100644 index 0000000..f43f47f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/cdesc-Excon.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/cdesc-Excon.ri new file mode 100644 index 0000000..9e8e26a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/cdesc-Excon.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/new-c.ri new file mode 100644 index 0000000..cf37f05 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/read_body-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/read_body-i.ri new file mode 100644 index 0000000..5cb55ea Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Excon/read_body-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/call-i.ri new file mode 100644 index 0000000..a6c2819 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/cdesc-HTTPClient.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/cdesc-HTTPClient.ri new file mode 100644 index 0000000..ef5a4d2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/cdesc-HTTPClient.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/client-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/client-i.ri new file mode 100644 index 0000000..a88427e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/client-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_proxy-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_proxy-i.ri new file mode 100644 index 0000000..ce15ae7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_proxy-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_socket-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_socket-i.ri new file mode 100644 index 0000000..17b2c08 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_socket-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_ssl-i.ri new file mode 100644 index 0000000..bdad4d4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_ssl-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_timeouts-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_timeouts-i.ri new file mode 100644 index 0000000..3e5daff Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/configure_timeouts-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/ssl_verify_mode-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/ssl_verify_mode-i.ri new file mode 100644 index 0000000..9c9d8a0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/HTTPClient/ssl_verify_mode-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/SSL/cdesc-SSL.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/SSL/cdesc-SSL.ri new file mode 100644 index 0000000..2d44fa8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/SSL/cdesc-SSL.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/cdesc-OpenSSL.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/cdesc-OpenSSL.ri new file mode 100644 index 0000000..12a4890 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/OpenSSL/cdesc-OpenSSL.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/call-i.ri new file mode 100644 index 0000000..a58b80f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/cdesc-NetHttp.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/cdesc-NetHttp.ri new file mode 100644 index 0000000..4207400 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/cdesc-NetHttp.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/configure_ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/configure_ssl-i.ri new file mode 100644 index 0000000..c89cb71 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/configure_ssl-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/create_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/create_request-i.ri new file mode 100644 index 0000000..3faf730 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/create_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/net_http_connection-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/net_http_connection-i.ri new file mode 100644 index 0000000..374a7dc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/net_http_connection-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/perform_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/perform_request-i.ri new file mode 100644 index 0000000..a764361 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/perform_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_cert_store-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_cert_store-i.ri new file mode 100644 index 0000000..1ba08df Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_cert_store-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_verify_mode-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_verify_mode-i.ri new file mode 100644 index 0000000..71bd93b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttp/ssl_verify_mode-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/cdesc-NetHttpPersistent.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/cdesc-NetHttpPersistent.ri new file mode 100644 index 0000000..4d9087c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/cdesc-NetHttpPersistent.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/configure_ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/configure_ssl-i.ri new file mode 100644 index 0000000..b4b3063 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/configure_ssl-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/net_http_connection-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/net_http_connection-i.ri new file mode 100644 index 0000000..116e22c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/net_http_connection-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/perform_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/perform_request-i.ri new file mode 100644 index 0000000..d0a991f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/perform_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/proxy_uri;/cdesc-proxy_uri;.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/proxy_uri;/cdesc-proxy_uri;.ri new file mode 100644 index 0000000..eb44621 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/NetHttpPersistent/proxy_uri;/cdesc-proxy_uri;.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/cdesc-Parallelism.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/cdesc-Parallelism.ri new file mode 100644 index 0000000..777db80 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/cdesc-Parallelism.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/inherited-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/inherited-i.ri new file mode 100644 index 0000000..e08ced6 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/inherited-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel%3f-i.ri new file mode 100644 index 0000000..4646326 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel-i.ri new file mode 100644 index 0000000..3570933 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Parallelism/supports_parallel-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/Request/cdesc-Request.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/Request/cdesc-Request.ri new file mode 100644 index 0000000..2705c5a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/Request/cdesc-Request.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/call-i.ri new file mode 100644 index 0000000..dd2256f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/cdesc-Patron.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/cdesc-Patron.ri new file mode 100644 index 0000000..b75ddbf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/cdesc-Patron.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/create_session-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/create_session-i.ri new file mode 100644 index 0000000..cb7dafd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/create_session-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/new-c.ri new file mode 100644 index 0000000..18a486b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Patron/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/call-i.ri new file mode 100644 index 0000000..8383d29 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/cdesc-Rack.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/cdesc-Rack.ri new file mode 100644 index 0000000..d992581 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/cdesc-Rack.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/execute_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/execute_request-i.ri new file mode 100644 index 0000000..f7e77ce Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/execute_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/new-c.ri new file mode 100644 index 0000000..41b548a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Rack/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/cdesc-Stub.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/cdesc-Stub.ri new file mode 100644 index 0000000..ab9968b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/cdesc-Stub.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/headers_match%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/headers_match%3f-i.ri new file mode 100644 index 0000000..02e8ab4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/headers_match%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/matches%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/matches%3f-i.ri new file mode 100644 index 0000000..d445d9f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/matches%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/new-c.ri new file mode 100644 index 0000000..4a5c223 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/params_match%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/params_match%3f-i.ri new file mode 100644 index 0000000..1dac572 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/params_match%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/to_s-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/to_s-i.ri new file mode 100644 index 0000000..a01ae92 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stub/to_s-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/NotFound/cdesc-NotFound.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/NotFound/cdesc-NotFound.ri new file mode 100644 index 0000000..5a571d2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/NotFound/cdesc-NotFound.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/cdesc-Stubs.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/cdesc-Stubs.ri new file mode 100644 index 0000000..fbd7f63 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/cdesc-Stubs.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/delete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/delete-i.ri new file mode 100644 index 0000000..5e770a4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/delete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/empty%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/empty%3f-i.ri new file mode 100644 index 0000000..73b2e76 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/empty%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/get-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/get-i.ri new file mode 100644 index 0000000..e0a6a1a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/get-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/head-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/head-i.ri new file mode 100644 index 0000000..c290890 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/head-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/match-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/match-i.ri new file mode 100644 index 0000000..9914055 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/match-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/matches%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/matches%3f-i.ri new file mode 100644 index 0000000..82de4bb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/matches%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new-c.ri new file mode 100644 index 0000000..50e5a42 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new_stub-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new_stub-i.ri new file mode 100644 index 0000000..43b1f4d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/new_stub-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/options-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/options-i.ri new file mode 100644 index 0000000..479d5e8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/options-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/patch-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/patch-i.ri new file mode 100644 index 0000000..49386e5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/patch-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/post-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/post-i.ri new file mode 100644 index 0000000..bcf7701 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/post-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/put-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/put-i.ri new file mode 100644 index 0000000..3ba0539 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/put-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/verify_stubbed_calls-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/verify_stubbed_calls-i.ri new file mode 100644 index 0000000..891c71f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/Stubs/verify_stubbed_calls-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/call-i.ri new file mode 100644 index 0000000..d7cc10e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/cdesc-Test.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/cdesc-Test.ri new file mode 100644 index 0000000..f201ce0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/cdesc-Test.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/configure-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/configure-i.ri new file mode 100644 index 0000000..30af018 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/configure-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/new-c.ri new file mode 100644 index 0000000..d466713 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/stubs-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/stubs-i.ri new file mode 100644 index 0000000..9989230 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Test/stubs-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/call-i.ri new file mode 100644 index 0000000..2c9d2c3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/cdesc-Typhoeus.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/cdesc-Typhoeus.ri new file mode 100644 index 0000000..efef68b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/cdesc-Typhoeus.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_proxy-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_proxy-i.ri new file mode 100644 index 0000000..8277172 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_proxy-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_socket-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_socket-i.ri new file mode 100644 index 0000000..7645571 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_socket-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_ssl-i.ri new file mode 100644 index 0000000..5d56c3c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_ssl-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_timeout-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_timeout-i.ri new file mode 100644 index 0000000..1964196 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/configure_timeout-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/parallel%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/parallel%3f-i.ri new file mode 100644 index 0000000..038f9b7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/parallel%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/perform_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/perform_request-i.ri new file mode 100644 index 0000000..ae60883 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/perform_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/read_body-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/read_body-i.ri new file mode 100644 index 0000000..1f2017a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/read_body-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request-i.ri new file mode 100644 index 0000000..b9b30e9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request_options-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request_options-i.ri new file mode 100644 index 0000000..fce6542 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/request_options-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/setup_parallel_manager-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/setup_parallel_manager-c.ri new file mode 100644 index 0000000..c65a2bc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/Typhoeus/setup_parallel_manager-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/call-i.ri new file mode 100644 index 0000000..7d3ccd9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/cdesc-Adapter.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/cdesc-Adapter.ri new file mode 100644 index 0000000..405ba77 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/cdesc-Adapter.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/save_response-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/save_response-i.ri new file mode 100644 index 0000000..2a381b8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Adapter/save_response-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/all_loaded_constants-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/all_loaded_constants-i.ri new file mode 100644 index 0000000..4b5ae51 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/all_loaded_constants-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/autoload_all-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/autoload_all-i.ri new file mode 100644 index 0000000..36a6f2f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/autoload_all-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/cdesc-AutoloadHelper.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/cdesc-AutoloadHelper.ri new file mode 100644 index 0000000..17e5040 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/cdesc-AutoloadHelper.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/load_autoloaded_constants-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/load_autoloaded_constants-i.ri new file mode 100644 index 0000000..833524c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/AutoloadHelper/load_autoloaded_constants-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/backtrace-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/backtrace-i.ri new file mode 100644 index 0000000..58ec4ab Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/backtrace-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/cdesc-ClientError.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/cdesc-ClientError.ri new file mode 100644 index 0000000..e4cff87 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/cdesc-ClientError.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/inspect-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/inspect-i.ri new file mode 100644 index 0000000..59ced11 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/inspect-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/new-c.ri new file mode 100644 index 0000000..da56ca9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/response-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/response-i.ri new file mode 100644 index 0000000..5a33073 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ClientError/response-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/advance_io-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/advance_io-i.ri new file mode 100644 index 0000000..d9b061b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/advance_io-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/cdesc-CompositeReadIO.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/cdesc-CompositeReadIO.ri new file mode 100644 index 0000000..6d66aa0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/cdesc-CompositeReadIO.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/close-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/close-i.ri new file mode 100644 index 0000000..b5997f4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/close-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/current_io-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/current_io-i.ri new file mode 100644 index 0000000..a7eaa85 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/current_io-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/ensure_open_and_readable-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/ensure_open_and_readable-i.ri new file mode 100644 index 0000000..e6d956d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/ensure_open_and_readable-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/length-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/length-i.ri new file mode 100644 index 0000000..518c9fe Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/length-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/new-c.ri new file mode 100644 index 0000000..cd735da Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/read-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/read-i.ri new file mode 100644 index 0000000..201dd0f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/read-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/rewind-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/rewind-i.ri new file mode 100644 index 0000000..f09ef4d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/CompositeReadIO/rewind-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/authorization-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/authorization-i.ri new file mode 100644 index 0000000..7294bbf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/authorization-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/basic_auth-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/basic_auth-i.ri new file mode 100644 index 0000000..06ecd0f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/basic_auth-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_exclusive_url-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_exclusive_url-i.ri new file mode 100644 index 0000000..11d4f0f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_exclusive_url-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_request-i.ri new file mode 100644 index 0000000..9b67faf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_url-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_url-i.ri new file mode 100644 index 0000000..2f2644f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/build_url-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/builder-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/builder-i.ri new file mode 100644 index 0000000..62a2405 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/builder-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" builder:ETI" Faraday::Connection#builder;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I">Public: Returns the Faraday::Builder for this Connection.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/cdesc-Connection.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/cdesc-Connection.ri new file mode 100644 index 0000000..171a407 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/cdesc-Connection.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/default_parallel_manager-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/default_parallel_manager-i.ri new file mode 100644 index 0000000..092e198 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/default_parallel_manager-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"default_parallel_manager:ETI"1Faraday::Connection#default_parallel_manager;FI"W;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"CPublic: Sets the default parallel manager for this connection.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/dup-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/dup-i.ri new file mode 100644 index 0000000..0ef42d6 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/dup-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers%3d-i.ri new file mode 100644 index 0000000..ddbf423 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers-i.ri new file mode 100644 index 0000000..854baeb --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/headers-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" headers:ETI" Faraday::Connection#headers;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"EPublic: Returns a Hash of unencoded HTTP header key/value pairs.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel%3f-i.ri new file mode 100644 index 0000000..dcad915 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel-i.ri new file mode 100644 index 0000000..1f87162 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/in_parallel-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/new-c.ri new file mode 100644 index 0000000..d2f0cdf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/options-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/options-i.ri new file mode 100644 index 0000000..88f9fc4 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/options-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" options:ETI" Faraday::Connection#options;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"3Public: Returns a Hash of the request options.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/parallel_manager-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/parallel_manager-i.ri new file mode 100644 index 0000000..6398498 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/parallel_manager-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"parallel_manager:ETI")Faraday::Connection#parallel_manager;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I">Public: Returns the parallel manager for this Connection.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params%3d-i.ri new file mode 100644 index 0000000..2067ea0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params-i.ri new file mode 100644 index 0000000..f125fed --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/params-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" params:ETI"Faraday::Connection#params;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"CPublic: Returns a Hash of URI query unencoded key/value pairs.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/path_prefix%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/path_prefix%3d-i.ri new file mode 100644 index 0000000..cada069 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/path_prefix%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/proxy-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/proxy-i.ri new file mode 100644 index 0000000..3c13ee1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/proxy-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/run_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/run_request-i.ri new file mode 100644 index 0000000..896c818 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/run_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/set_authorization_header-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/set_authorization_header-i.ri new file mode 100644 index 0000000..7e5a695 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/set_authorization_header-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/ssl-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/ssl-i.ri new file mode 100644 index 0000000..57b3c56 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/ssl-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"ssl:ETI"Faraday::Connection#ssl;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"/Public: Returns a Hash of the SSL options.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/token_auth-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/token_auth-i.ri new file mode 100644 index 0000000..7d0d72e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/token_auth-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix%3d-i.ri new file mode 100644 index 0000000..9a67aa8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix-i.ri new file mode 100644 index 0000000..0a6823f --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/url_prefix-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"url_prefix:ETI"#Faraday::Connection#url_prefix;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"KPublic: Returns a URI with the prefix used for all requests from this ;TI"LConnection. This includes a default host name, scheme, port, and path.;T: +@fileI"lib/faraday/connection.rb;T:0@omit_headings_from_table_of_contents_below0F@I"Faraday::Connection;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/with_uri_credentials-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/with_uri_credentials-i.ri new file mode 100644 index 0000000..d9cb527 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Connection/with_uri_credentials-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionFailed/cdesc-ConnectionFailed.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionFailed/cdesc-ConnectionFailed.ri new file mode 100644 index 0000000..ad50f2e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionFailed/cdesc-ConnectionFailed.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/cdesc-ConnectionOptions.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/cdesc-ConnectionOptions.ri new file mode 100644 index 0000000..51e6d02 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/cdesc-ConnectionOptions.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/new_builder-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/new_builder-i.ri new file mode 100644 index 0000000..f9c6b91 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ConnectionOptions/new_builder-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d%3d-i.ri new file mode 100644 index 0000000..00f4815 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d-i.ri new file mode 100644 index 0000000..d44fbbf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/Utils/cdesc-Utils.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/Utils/cdesc-Utils.ri new file mode 100644 index 0000000..54f28bb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/Utils/cdesc-Utils.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/cdesc-Env.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/cdesc-Env.ri new file mode 100644 index 0000000..34dcd94 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/cdesc-Env.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/clear_body-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/clear_body-i.ri new file mode 100644 index 0000000..538843e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/clear_body-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/custom_members-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/custom_members-i.ri new file mode 100644 index 0000000..b08291c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/custom_members-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/in_member_set%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/in_member_set%3f-i.ri new file mode 100644 index 0000000..34e1674 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/in_member_set%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/inspect-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/inspect-i.ri new file mode 100644 index 0000000..d3a6a87 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/inspect-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/member_set-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/member_set-c.ri new file mode 100644 index 0000000..2da5f1c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/member_set-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/needs_body%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/needs_body%3f-i.ri new file mode 100644 index 0000000..790b19d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/needs_body%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parallel%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parallel%3f-i.ri new file mode 100644 index 0000000..d4b3d28 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parallel%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parse_body%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parse_body%3f-i.ri new file mode 100644 index 0000000..0145627 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/parse_body%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Env/success%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/success%3f-i.ri new file mode 100644 index 0000000..323ada8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Env/success%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Error/cdesc-Error.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Error/cdesc-Error.ri new file mode 100644 index 0000000..1308628 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Error/cdesc-Error.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/cdesc-FlatParamsEncoder.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/cdesc-FlatParamsEncoder.ri new file mode 100644 index 0000000..e8818c1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/cdesc-FlatParamsEncoder.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/decode-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/decode-c.ri new file mode 100644 index 0000000..5824466 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/decode-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/encode-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/encode-c.ri new file mode 100644 index 0000000..fd364bc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/encode-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/escape-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/escape-c.ri new file mode 100644 index 0000000..9d08279 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/escape-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/unescape-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/unescape-c.ri new file mode 100644 index 0000000..978e6de Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/FlatParamsEncoder/unescape-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/cdesc-Middleware.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/cdesc-Middleware.ri new file mode 100644 index 0000000..e18f421 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/cdesc-Middleware.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/dependency-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/dependency-c.ri new file mode 100644 index 0000000..5b3312a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/dependency-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/inherited-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/inherited-c.ri new file mode 100644 index 0000000..67474b5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/inherited-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/load_error-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/load_error-c.ri new file mode 100644 index 0000000..ddaa373 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/load_error-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/loaded%3f-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/loaded%3f-c.ri new file mode 100644 index 0000000..9925119 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/loaded%3f-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/new-c.ri new file mode 100644 index 0000000..e63cf4c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Middleware/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/cdesc-MiddlewareRegistry.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/cdesc-MiddlewareRegistry.ri new file mode 100644 index 0000000..f5ba1a0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/cdesc-MiddlewareRegistry.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/fetch_middleware-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/fetch_middleware-i.ri new file mode 100644 index 0000000..7816bd0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/fetch_middleware-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/load_middleware-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/load_middleware-i.ri new file mode 100644 index 0000000..5079d3a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/load_middleware-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/lookup_middleware-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/lookup_middleware-i.ri new file mode 100644 index 0000000..f9bb6cb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/lookup_middleware-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/middleware_mutex-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/middleware_mutex-i.ri new file mode 100644 index 0000000..7120014 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/middleware_mutex-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/register_middleware-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/register_middleware-i.ri new file mode 100644 index 0000000..fb84ed2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MiddlewareRegistry/register_middleware-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/MissingDependency/cdesc-MissingDependency.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/MissingDependency/cdesc-MissingDependency.ri new file mode 100644 index 0000000..475e9a0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/MissingDependency/cdesc-MissingDependency.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/cdesc-NestedParamsEncoder.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/cdesc-NestedParamsEncoder.ri new file mode 100644 index 0000000..628aa03 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/cdesc-NestedParamsEncoder.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/decode-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/decode-c.ri new file mode 100644 index 0000000..8484c88 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/decode-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/encode-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/encode-c.ri new file mode 100644 index 0000000..e4a3758 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/encode-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/escape-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/escape-c.ri new file mode 100644 index 0000000..f18bee7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/escape-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/unescape-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/unescape-c.ri new file mode 100644 index 0000000..1e3f928 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/NestedParamsEncoder/unescape-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/%5b%5d-i.ri new file mode 100644 index 0000000..fb98343 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/attribute_options-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/attribute_options-c.ri new file mode 100644 index 0000000..e68021b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/attribute_options-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/cdesc-Options.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/cdesc-Options.ri new file mode 100644 index 0000000..58ae3cf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/cdesc-Options.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/clear-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/clear-i.ri new file mode 100644 index 0000000..9f1b879 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/clear-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/delete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/delete-i.ri new file mode 100644 index 0000000..d507a66 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/delete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each-i.ri new file mode 100644 index 0000000..6737041 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_key-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_key-i.ri new file mode 100644 index 0000000..e7aa075 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_key-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_value-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_value-i.ri new file mode 100644 index 0000000..f39ed0b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/each_value-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/empty%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/empty%3f-i.ri new file mode 100644 index 0000000..5665894 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/empty%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch-i.ri new file mode 100644 index 0000000..946729f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch_error_class-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch_error_class-c.ri new file mode 100644 index 0000000..1003833 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/fetch_error_class-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/from-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/from-c.ri new file mode 100644 index 0000000..4c6da41 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/from-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_key%3f-i.ri new file mode 100644 index 0000000..c158a31 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_value%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_value%3f-i.ri new file mode 100644 index 0000000..2450766 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/has_value%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inherited-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inherited-c.ri new file mode 100644 index 0000000..70072df Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inherited-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inspect-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inspect-i.ri new file mode 100644 index 0000000..2e26a5a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/inspect-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/key%3f-i.ri new file mode 100644 index 0000000..934ac95 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/keys-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/keys-i.ri new file mode 100644 index 0000000..579df8a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/keys-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized-c.ri new file mode 100644 index 0000000..87e8f75 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized_attributes-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized_attributes-c.ri new file mode 100644 index 0000000..f1cd43c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/memoized_attributes-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge%21-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge%21-i.ri new file mode 100644 index 0000000..aa2b920 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge%21-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge-i.ri new file mode 100644 index 0000000..1fed74a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/merge-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options-c.ri new file mode 100644 index 0000000..52a4637 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options_for-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options_for-c.ri new file mode 100644 index 0000000..ffe7290 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/options_for-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/symbolized_key_set-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/symbolized_key_set-i.ri new file mode 100644 index 0000000..38fbf75 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/symbolized_key_set-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/to_hash-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/to_hash-i.ri new file mode 100644 index 0000000..0ea4e38 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/to_hash-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/update-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/update-i.ri new file mode 100644 index 0000000..3f56841 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/update-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/value%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/value%3f-i.ri new file mode 100644 index 0000000..58f5f6f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/value%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Options/values_at-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/values_at-i.ri new file mode 100644 index 0000000..e55bf71 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Options/values_at-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ParsingError/cdesc-ParsingError.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ParsingError/cdesc-ParsingError.ri new file mode 100644 index 0000000..a820abc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ParsingError/cdesc-ParsingError.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/cdesc-ProxyOptions.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/cdesc-ProxyOptions.ri new file mode 100644 index 0000000..22d3a9b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/cdesc-ProxyOptions.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/from-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/from-c.ri new file mode 100644 index 0000000..13d8644 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ProxyOptions/from-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%3d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%3d%3d-i.ri new file mode 100644 index 0000000..5528cd4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%3d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%5b%5d-i.ri new file mode 100644 index 0000000..96c9681 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/%3d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/%3d%3d-i.ri new file mode 100644 index 0000000..fa39ab0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/%3d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/build-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/build-i.ri new file mode 100644 index 0000000..6835edf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/build-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/cdesc-Handler.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/cdesc-Handler.ri new file mode 100644 index 0000000..c8f175c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/cdesc-Handler.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/inspect-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/inspect-i.ri new file mode 100644 index 0000000..8deb6a0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/inspect-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/klass-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/klass-i.ri new file mode 100644 index 0000000..dc05d6f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/klass-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/name-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/name-i.ri new file mode 100644 index 0000000..fac10d3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/name-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/new-c.ri new file mode 100644 index 0000000..cc96c38 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/Handler/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/StackLocked/cdesc-StackLocked.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/StackLocked/cdesc-StackLocked.ri new file mode 100644 index 0000000..4c0d921 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/StackLocked/cdesc-StackLocked.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/adapter-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/adapter-i.ri new file mode 100644 index 0000000..f8439c6 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/adapter-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/app-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/app-i.ri new file mode 100644 index 0000000..eefcdfc Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/app-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/assert_index-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/assert_index-i.ri new file mode 100644 index 0000000..e8001b9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/assert_index-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build-i.ri new file mode 100644 index 0000000..7f6160d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_env-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_env-i.ri new file mode 100644 index 0000000..a5eb6c4 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_env-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_response-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_response-i.ri new file mode 100644 index 0000000..d07221e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/build_response-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/cdesc-RackBuilder.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/cdesc-RackBuilder.ri new file mode 100644 index 0000000..2f0dc02 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/cdesc-RackBuilder.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/delete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/delete-i.ri new file mode 100644 index 0000000..90563eb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/delete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/dup-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/dup-i.ri new file mode 100644 index 0000000..a794d85 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/dup-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/handlers-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/handlers-i.ri new file mode 100644 index 0000000..08aa557 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/handlers-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert-i.ri new file mode 100644 index 0000000..3c2dc97 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_after-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_after-i.ri new file mode 100644 index 0000000..2fa01c7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_after-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_before-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_before-i.ri new file mode 100644 index 0000000..1f33a97 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/insert_before-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/lock%21-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/lock%21-i.ri new file mode 100644 index 0000000..ed1a759 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/lock%21-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/locked%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/locked%3f-i.ri new file mode 100644 index 0000000..6de6c42 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/locked%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/new-c.ri new file mode 100644 index 0000000..55d9b78 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/raise_if_locked-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/raise_if_locked-i.ri new file mode 100644 index 0000000..4d7ddb1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/raise_if_locked-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/request-i.ri new file mode 100644 index 0000000..1eb68d3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/response-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/response-i.ri new file mode 100644 index 0000000..8faa188 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/response-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/swap-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/swap-i.ri new file mode 100644 index 0000000..719b7af Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/swap-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/to_app-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/to_app-i.ri new file mode 100644 index 0000000..1b3f4fb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/to_app-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use-i.ri new file mode 100644 index 0000000..feea659 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use_symbol-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use_symbol-i.ri new file mode 100644 index 0000000..4ff0f21 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RackBuilder/use_symbol-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d%3d-i.ri new file mode 100644 index 0000000..842d65f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d-i.ri new file mode 100644 index 0000000..6a329c1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/build_hash-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/build_hash-c.ri new file mode 100644 index 0000000..8c469e7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/build_hash-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/call-i.ri new file mode 100644 index 0000000..a70eb0f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/cdesc-Authorization.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/cdesc-Authorization.ri new file mode 100644 index 0000000..04a9b8c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/cdesc-Authorization.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/header-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/header-c.ri new file mode 100644 index 0000000..fdb23cd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/header-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/new-c.ri new file mode 100644 index 0000000..8d906d5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Authorization/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/cdesc-BasicAuthentication.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/cdesc-BasicAuthentication.ri new file mode 100644 index 0000000..5685c5d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/cdesc-BasicAuthentication.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/header-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/header-c.ri new file mode 100644 index 0000000..55bb6ca Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/BasicAuthentication/header-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/cdesc-Options.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/cdesc-Options.ri new file mode 100644 index 0000000..482b37d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/cdesc-Options.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/instrumenter-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/instrumenter-i.ri new file mode 100644 index 0000000..fdf231f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/instrumenter-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/name-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/name-i.ri new file mode 100644 index 0000000..1657d73 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/Options/name-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/call-i.ri new file mode 100644 index 0000000..7f81aae Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/cdesc-Instrumentation.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/cdesc-Instrumentation.ri new file mode 100644 index 0000000..9d4796a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/cdesc-Instrumentation.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/new-c.ri new file mode 100644 index 0000000..a2b5b9a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Instrumentation/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/call-i.ri new file mode 100644 index 0000000..a1d0bd2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/cdesc-Multipart.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/cdesc-Multipart.ri new file mode 100644 index 0000000..61d487a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/cdesc-Multipart.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/create_multipart-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/create_multipart-i.ri new file mode 100644 index 0000000..5081047 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/create_multipart-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/has_multipart%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/has_multipart%3f-i.ri new file mode 100644 index 0000000..1fa80df Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/has_multipart%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_params-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_params-i.ri new file mode 100644 index 0000000..8efbc56 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_params-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_request%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_request%3f-i.ri new file mode 100644 index 0000000..195957f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Multipart/process_request%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/backoff_factor-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/backoff_factor-i.ri new file mode 100644 index 0000000..36c9838 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/backoff_factor-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/cdesc-Options.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/cdesc-Options.ri new file mode 100644 index 0000000..e336209 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/cdesc-Options.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/exceptions-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/exceptions-i.ri new file mode 100644 index 0000000..8603480 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/exceptions-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/from-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/from-c.ri new file mode 100644 index 0000000..c2d87fe Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/from-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval-i.ri new file mode 100644 index 0000000..af78be0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval_randomness-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval_randomness-i.ri new file mode 100644 index 0000000..d298dd5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/interval_randomness-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/max-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/max-i.ri new file mode 100644 index 0000000..14a3571 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/Options/max-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/build_exception_matcher-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/build_exception_matcher-i.ri new file mode 100644 index 0000000..02476dd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/build_exception_matcher-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/call-i.ri new file mode 100644 index 0000000..b7be1a2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/cdesc-Retry.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/cdesc-Retry.ri new file mode 100644 index 0000000..723ecdf Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/cdesc-Retry.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/matcher;/cdesc-matcher;.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/matcher;/cdesc-matcher;.ri new file mode 100644 index 0000000..9e847b0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/matcher;/cdesc-matcher;.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/new-c.ri new file mode 100644 index 0000000..85c0e02 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/sleep_amount-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/sleep_amount-i.ri new file mode 100644 index 0000000..c2124c0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/Retry/sleep_amount-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/cdesc-TokenAuthentication.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/cdesc-TokenAuthentication.ri new file mode 100644 index 0000000..cef314f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/cdesc-TokenAuthentication.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/header-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/header-c.ri new file mode 100644 index 0000000..9829d3c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/header-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/new-c.ri new file mode 100644 index 0000000..dde0a5c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/TokenAuthentication/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/call-i.ri new file mode 100644 index 0000000..01e16da Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/cdesc-UrlEncoded.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/cdesc-UrlEncoded.ri new file mode 100644 index 0000000..5024e95 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/cdesc-UrlEncoded.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/match_content_type-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/match_content_type-i.ri new file mode 100644 index 0000000..564ab84 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/match_content_type-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/mime_type-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/mime_type-c.ri new file mode 100644 index 0000000..9803c97 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/mime_type-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/process_request%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/process_request%3f-i.ri new file mode 100644 index 0000000..17b028b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/process_request%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/request_type-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/request_type-i.ri new file mode 100644 index 0000000..74eba93 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/UrlEncoded/request_type-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/cdesc-Request.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/cdesc-Request.ri new file mode 100644 index 0000000..02bb8d6 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/cdesc-Request.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/create-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/create-c.ri new file mode 100644 index 0000000..3e98419 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/create-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/headers%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/headers%3d-i.ri new file mode 100644 index 0000000..b043483 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/headers%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/params%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/params%3d-i.ri new file mode 100644 index 0000000..6c518de Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/params%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/to_env-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/to_env-i.ri new file mode 100644 index 0000000..ead007c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/to_env-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Request/url-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/url-i.ri new file mode 100644 index 0000000..376ac5c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Request/url-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/%5b%5d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/%5b%5d%3d-i.ri new file mode 100644 index 0000000..d305f5f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/cdesc-RequestOptions.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/cdesc-RequestOptions.ri new file mode 100644 index 0000000..c0434a3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/RequestOptions/cdesc-RequestOptions.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/ResourceNotFound/cdesc-ResourceNotFound.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/ResourceNotFound/cdesc-ResourceNotFound.ri new file mode 100644 index 0000000..c41fe56 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/ResourceNotFound/cdesc-ResourceNotFound.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/call-i.ri new file mode 100644 index 0000000..60f8701 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/cdesc-Logger.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/cdesc-Logger.ri new file mode 100644 index 0000000..facfc36 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/cdesc-Logger.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/dump_headers-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/dump_headers-i.ri new file mode 100644 index 0000000..9374d84 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/dump_headers-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/new-c.ri new file mode 100644 index 0000000..0c54345 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/on_complete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/on_complete-i.ri new file mode 100644 index 0000000..e888d03 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Logger/on_complete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/call-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/call-i.ri new file mode 100644 index 0000000..b07614c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/call-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/cdesc-Middleware.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/cdesc-Middleware.ri new file mode 100644 index 0000000..97b4782 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/cdesc-Middleware.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/on_complete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/on_complete-i.ri new file mode 100644 index 0000000..402522e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/Middleware/on_complete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/cdesc-RaiseError.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/cdesc-RaiseError.ri new file mode 100644 index 0000000..0e59471 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/cdesc-RaiseError.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/on_complete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/on_complete-i.ri new file mode 100644 index 0000000..d0a7839 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/on_complete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/response_values-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/response_values-i.ri new file mode 100644 index 0000000..1389b49 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/RaiseError/response_values-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/apply_request-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/apply_request-i.ri new file mode 100644 index 0000000..df04492 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/apply_request-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/body-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/body-i.ri new file mode 100644 index 0000000..99ad14d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/body-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/cdesc-Response.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/cdesc-Response.ri new file mode 100644 index 0000000..327b53b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/cdesc-Response.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/env-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/env-i.ri new file mode 100644 index 0000000..c91268c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/env-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finish-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finish-i.ri new file mode 100644 index 0000000..c722c4f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finish-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finished%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finished%3f-i.ri new file mode 100644 index 0000000..6e5c684 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/finished%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/headers-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/headers-i.ri new file mode 100644 index 0000000..5e081a9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/headers-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_dump-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_dump-i.ri new file mode 100644 index 0000000..2a4e89b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_dump-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_load-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_load-i.ri new file mode 100644 index 0000000..f396484 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/marshal_load-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/new-c.ri new file mode 100644 index 0000000..03335a3 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/on_complete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/on_complete-i.ri new file mode 100644 index 0000000..7dcbe62 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/on_complete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/status-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/status-i.ri new file mode 100644 index 0000000..477867d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/status-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Response/success%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/success%3f-i.ri new file mode 100644 index 0000000..839311f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Response/success%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/SSLError/cdesc-SSLError.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLError/cdesc-SSLError.ri new file mode 100644 index 0000000..013a849 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLError/cdesc-SSLError.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/cdesc-SSLOptions.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/cdesc-SSLOptions.ri new file mode 100644 index 0000000..b1a334f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/cdesc-SSLOptions.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/disable%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/disable%3f-i.ri new file mode 100644 index 0000000..1325104 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/disable%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/verify%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/verify%3f-i.ri new file mode 100644 index 0000000..6e000a2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/SSLOptions/verify%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/cdesc-TimeoutError.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/cdesc-TimeoutError.ri new file mode 100644 index 0000000..9240f8f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/cdesc-TimeoutError.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/new-c.ri new file mode 100644 index 0000000..b94a1ee Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/TimeoutError/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d%3d-i.ri new file mode 100644 index 0000000..9142c92 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d-i.ri new file mode 100644 index 0000000..b2b4514 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/cdesc-Headers.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/cdesc-Headers.ri new file mode 100644 index 0000000..f6a3e42 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/cdesc-Headers.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/delete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/delete-i.ri new file mode 100644 index 0000000..fd79592 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/delete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/fetch-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/fetch-i.ri new file mode 100644 index 0000000..1e08fca Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/fetch-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/from-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/from-c.ri new file mode 100644 index 0000000..8d92079 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/from-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/has_key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/has_key%3f-i.ri new file mode 100644 index 0000000..f51f57f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/has_key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/include%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/include%3f-i.ri new file mode 100644 index 0000000..312512a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/include%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/key%3f-i.ri new file mode 100644 index 0000000..55f42ae Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/member%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/member%3f-i.ri new file mode 100644 index 0000000..9ea9419 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/member%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge%21-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge%21-i.ri new file mode 100644 index 0000000..3ee3bef Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge%21-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge-i.ri new file mode 100644 index 0000000..79d3946 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/merge-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/new-c.ri new file mode 100644 index 0000000..fb3f32d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/parse-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/parse-i.ri new file mode 100644 index 0000000..8cb9b7a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/parse-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/replace-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/replace-i.ri new file mode 100644 index 0000000..2ecd61a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/replace-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/to_hash-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/to_hash-i.ri new file mode 100644 index 0000000..5c4e8cd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/to_hash-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/update-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/update-i.ri new file mode 100644 index 0000000..e31c77f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/Headers/update-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d%3d-i.ri new file mode 100644 index 0000000..12d5732 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d-i.ri new file mode 100644 index 0000000..167a88b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/%5b%5d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/cdesc-ParamsHash.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/cdesc-ParamsHash.ri new file mode 100644 index 0000000..e170d42 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/cdesc-ParamsHash.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/convert_key-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/convert_key-i.ri new file mode 100644 index 0000000..149be3c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/convert_key-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/delete-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/delete-i.ri new file mode 100644 index 0000000..12ca127 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/delete-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/has_key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/has_key%3f-i.ri new file mode 100644 index 0000000..ab836d9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/has_key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/include%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/include%3f-i.ri new file mode 100644 index 0000000..6e3d4e1 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/include%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/key%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/key%3f-i.ri new file mode 100644 index 0000000..24c19bb Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/key%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/member%3f-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/member%3f-i.ri new file mode 100644 index 0000000..679e8be Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/member%3f-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge%21-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge%21-i.ri new file mode 100644 index 0000000..4a689cd Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge%21-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge-i.ri new file mode 100644 index 0000000..93e2249 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge_query-i.ri new file mode 100644 index 0000000..bf9b8c2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/merge_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/replace-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/replace-i.ri new file mode 100644 index 0000000..c8b4fe5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/replace-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/to_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/to_query-i.ri new file mode 100644 index 0000000..7b45c21 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/to_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/update-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/update-i.ri new file mode 100644 index 0000000..ea5b2d2 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/ParamsHash/update-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/URI-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/URI-i.ri new file mode 100644 index 0000000..aaa8a16 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/URI-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_nested_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_nested_query-i.ri new file mode 100644 index 0000000..c3d190a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_nested_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_query-i.ri new file mode 100644 index 0000000..70b1c4f Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/build_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/cdesc-Utils.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/cdesc-Utils.ri new file mode 100644 index 0000000..33f8dc8 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/cdesc-Utils.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge%21-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge%21-i.ri new file mode 100644 index 0000000..243101c Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge%21-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge-i.ri new file mode 100644 index 0000000..3de66d5 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/deep_merge-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-c.ri new file mode 100644 index 0000000..0d19346 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-i.ri new file mode 100644 index 0000000..122392b Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_params_encoder-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser%3d-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser%3d-i.ri new file mode 100644 index 0000000..7259e5a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser%3d-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser-i.ri new file mode 100644 index 0000000..2c70a9a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/default_uri_parser-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/escape-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/escape-i.ri new file mode 100644 index 0000000..9e2e23a Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/escape-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_params-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_params-i.ri new file mode 100644 index 0000000..86c0676 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_params-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_path-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_path-i.ri new file mode 100644 index 0000000..46d58b0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/normalize_path-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_nested_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_nested_query-i.ri new file mode 100644 index 0000000..46bd100 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_nested_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_query-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_query-i.ri new file mode 100644 index 0000000..9202a82 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/parse_query-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/sort_query_params-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/sort_query_params-i.ri new file mode 100644 index 0000000..307e0b9 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/sort_query_params-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/unescape-i.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/unescape-i.ri new file mode 100644 index 0000000..fc9ca97 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/Utils/unescape-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/cdesc-Faraday.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/cdesc-Faraday.ri new file mode 100644 index 0000000..a93723e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/cdesc-Faraday.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/const_missing-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/const_missing-c.ri new file mode 100644 index 0000000..c2c2e06 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/const_missing-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter%3d-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter%3d-c.ri new file mode 100644 index 0000000..313bb69 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter%3d-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter-c.ri new file mode 100644 index 0000000..d853fdc --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/default_adapter-c.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"default_adapter:ETI"Faraday::default_adapter;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"NPublic: Gets or sets the Symbol key identifying a default Adapter to use ;TI")for the default Faraday::Connection.;T: +@fileI"lib/faraday.rb;T:0@omit_headings_from_table_of_contents_below0T@I" Faraday;FcRDoc::NormalModule0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection-c.ri new file mode 100644 index 0000000..40a8b7d Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection_options-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection_options-c.ri new file mode 100644 index 0000000..c23b771 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/default_connection_options-c.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"default_connection_options:ETI"(Faraday::default_connection_options;FI"W;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"DPublic: Sets the default options used when calling Faraday#new.;T: +@fileI"lib/faraday.rb;T:0@omit_headings_from_table_of_contents_below0T@I" Faraday;FcRDoc::NormalModule0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/lib_path-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/lib_path-c.ri new file mode 100644 index 0000000..1ffedb5 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/lib_path-c.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" lib_path:ETI"Faraday::lib_path;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"IPublic: Gets or sets the path that the Faraday libs are loaded from.;T: +@fileI"lib/faraday.rb;T:0@omit_headings_from_table_of_contents_below0T@I" Faraday;FcRDoc::NormalModule0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/method_missing-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/method_missing-c.ri new file mode 100644 index 0000000..e9af071 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/method_missing-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/new-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/new-c.ri new file mode 100644 index 0000000..c8b40a0 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/new-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/require_lib-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/require_lib-c.ri new file mode 100644 index 0000000..029d823 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/require_lib-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/require_libs-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/require_libs-c.ri new file mode 100644 index 0000000..945836e Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Faraday/require_libs-c.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Faraday/root_path-c.ri b/.gems/doc/faraday-0.9.0/ri/Faraday/root_path-c.ri new file mode 100644 index 0000000..6938e11 --- /dev/null +++ b/.gems/doc/faraday-0.9.0/ri/Faraday/root_path-c.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"root_path:ETI"Faraday::root_path;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"KPublic: Gets or sets the root path that Faraday is being loaded from. ;TI"DThis is the root from where the libraries are auto-loaded from.;T: +@fileI"lib/faraday.rb;T:0@omit_headings_from_table_of_contents_below0T@I" Faraday;FcRDoc::NormalModule0 \ No newline at end of file diff --git a/.gems/doc/faraday-0.9.0/ri/Object/cdesc-Object.ri b/.gems/doc/faraday-0.9.0/ri/Object/cdesc-Object.ri new file mode 100644 index 0000000..1c6d137 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Object/cdesc-Object.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/Object/tap-i.ri b/.gems/doc/faraday-0.9.0/ri/Object/tap-i.ri new file mode 100644 index 0000000..32f1706 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/Object/tap-i.ri differ diff --git a/.gems/doc/faraday-0.9.0/ri/cache.ri b/.gems/doc/faraday-0.9.0/ri/cache.ri new file mode 100644 index 0000000..c5c85c7 Binary files /dev/null and b/.gems/doc/faraday-0.9.0/ri/cache.ri differ diff --git a/.gems/doc/http-0.6.2/ri/Base64/cdesc-Base64.ri b/.gems/doc/http-0.6.2/ri/Base64/cdesc-Base64.ri new file mode 100644 index 0000000..bb0f8db Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/Base64/cdesc-Base64.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/cdesc-BasicAuth.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/cdesc-BasicAuth.ri new file mode 100644 index 0000000..f39e285 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/cdesc-BasicAuth.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/new-c.ri new file mode 100644 index 0000000..59f5648 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BasicAuth/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/cdesc-BearerToken.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/cdesc-BearerToken.ri new file mode 100644 index 0000000..c97a82f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/cdesc-BearerToken.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/new-c.ri new file mode 100644 index 0000000..4072fb9 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/BearerToken/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/build-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/build-c.ri new file mode 100644 index 0000000..c2cf910 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/build-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/cdesc-AuthorizationHeader.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/cdesc-AuthorizationHeader.ri new file mode 100644 index 0000000..06dbdfd Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/cdesc-AuthorizationHeader.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/register-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/register-c.ri new file mode 100644 index 0000000..1cf65da Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/AuthorizationHeader/register-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/accept-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/accept-i.ri new file mode 100644 index 0000000..2546b20 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/accept-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/auth-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/auth-i.ri new file mode 100644 index 0000000..6ae6e5f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/auth-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/branch-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/branch-i.ri new file mode 100644 index 0000000..7d7e8f5 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/branch-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/cdesc-Chainable.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/cdesc-Chainable.ri new file mode 100644 index 0000000..cee866a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/cdesc-Chainable.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/connect-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/connect-i.ri new file mode 100644 index 0000000..251677a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/connect-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks%3d-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks%3d-i.ri new file mode 100644 index 0000000..c7c6de7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks%3d-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks-i.ri new file mode 100644 index 0000000..bababd3 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_callbacks-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers%3d-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers%3d-i.ri new file mode 100644 index 0000000..6c26ed1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers%3d-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers-i.ri new file mode 100644 index 0000000..fd20b64 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options%3d-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options%3d-i.ri new file mode 100644 index 0000000..8a2f025 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options%3d-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options-i.ri new file mode 100644 index 0000000..0baee6f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/default_options-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/delete-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/delete-i.ri new file mode 100644 index 0000000..d94899a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/delete-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/follow-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/follow-i.ri new file mode 100644 index 0000000..67ee078 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/follow-i.ri @@ -0,0 +1,3 @@ +U:RDoc::AnyMethod[iI" follow:EFI"HTTP::Chainable#follow;FF: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"#Make client follow redirects. ;TI"-@param opts (see Redirector#initialize) ;TI"@return [HTTP::Client];T: +@fileI"lib/http/chainable.rb;T:0@omit_headings_from_table_of_contents_below000[[I"with_follow;To;; [o; +; [I"(see #follow) ;TI"@deprecated;T; @; 0I"(opts = true);T@FI"Chainable;FcRDoc::NormalModule0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/get-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/get-i.ri new file mode 100644 index 0000000..3bef440 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/get-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/head-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/head-i.ri new file mode 100644 index 0000000..a230c1a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/head-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/options-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/options-i.ri new file mode 100644 index 0000000..c85bd33 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/options-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/patch-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/patch-i.ri new file mode 100644 index 0000000..bc53397 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/patch-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/post-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/post-i.ri new file mode 100644 index 0000000..f88f9e0 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/post-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/put-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/put-i.ri new file mode 100644 index 0000000..b2e24f7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/put-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/request-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/request-i.ri new file mode 100644 index 0000000..1f0912f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/request-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/stream-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/stream-i.ri new file mode 100644 index 0000000..d1bab5d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/stream-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/through-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/through-i.ri new file mode 100644 index 0000000..6ba65b7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/through-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/trace-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/trace-i.ri new file mode 100644 index 0000000..a32f3c6 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/trace-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/via-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/via-i.ri new file mode 100644 index 0000000..1e98e90 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/via-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with-i.ri new file mode 100644 index 0000000..3d77a39 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_follow-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_follow-i.ri new file mode 100644 index 0000000..28edbae Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_follow-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_headers-i.ri new file mode 100644 index 0000000..2e14459 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Chainable/with_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/cdesc-Client.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/cdesc-Client.ri new file mode 100644 index 0000000..9f6457b Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/cdesc-Client.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/default_options-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/default_options-i.ri new file mode 100644 index 0000000..d439087 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/default_options-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/finish_response-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/finish_response-i.ri new file mode 100644 index 0000000..a921f20 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/finish_response-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_body-i.ri new file mode 100644 index 0000000..253c216 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_body-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_uri-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_uri-i.ri new file mode 100644 index 0000000..03f74ac Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/make_request_uri-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/new-c.ri new file mode 100644 index 0000000..dab7093 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/perform-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/perform-i.ri new file mode 100644 index 0000000..1f4ba92 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/perform-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/read_more-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/read_more-i.ri new file mode 100644 index 0000000..4626461 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/read_more-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/readpartial-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/readpartial-i.ri new file mode 100644 index 0000000..47b6a95 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/readpartial-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/request-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/request-i.ri new file mode 100644 index 0000000..a885d84 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/request-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Client/start_tls-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Client/start_tls-i.ri new file mode 100644 index 0000000..563dbaf Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Client/start_tls-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Error/cdesc-Error.ri b/.gems/doc/http-0.6.2/ri/HTTP/Error/cdesc-Error.ri new file mode 100644 index 0000000..e207813 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Error/cdesc-Error.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/cdesc-Mixin.ri b/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/cdesc-Mixin.ri new file mode 100644 index 0000000..21b7ffe Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/cdesc-Mixin.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/headers-i.ri new file mode 100644 index 0000000..fee04f4 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Headers/Mixin/headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Headers/cdesc-Headers.ri b/.gems/doc/http-0.6.2/ri/HTTP/Headers/cdesc-Headers.ri new file mode 100644 index 0000000..1ab4cc1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Headers/cdesc-Headers.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/%5b%5d-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/%5b%5d-c.ri new file mode 100644 index 0000000..84f09e1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/%5b%5d-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/Adapter/cdesc-Adapter.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/Adapter/cdesc-Adapter.ri new file mode 100644 index 0000000..0faa896 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/Adapter/cdesc-Adapter.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/cdesc-JSON.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/cdesc-JSON.ri new file mode 100644 index 0000000..7c86a2b Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/cdesc-JSON.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/decode-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/decode-i.ri new file mode 100644 index 0000000..62424b4 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/decode-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/encode-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/encode-i.ri new file mode 100644 index 0000000..879a4e6 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/JSON/encode-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/cdesc-MimeType.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/cdesc-MimeType.ri new file mode 100644 index 0000000..78e27ac Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/cdesc-MimeType.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/normalize-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/normalize-c.ri new file mode 100644 index 0000000..2b64f77 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/normalize-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_adapter-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_adapter-c.ri new file mode 100644 index 0000000..0bf6b94 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_adapter-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_alias-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_alias-c.ri new file mode 100644 index 0000000..12d4faf Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/MimeType/register_alias-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/%5b%5d-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/%5b%5d-i.ri new file mode 100644 index 0000000..9023997 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/%5b%5d-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/SSL/cdesc-SSL.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/SSL/cdesc-SSL.ri new file mode 100644 index 0000000..7750077 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/SSL/cdesc-SSL.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/cdesc-OpenSSL.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/cdesc-OpenSSL.ri new file mode 100644 index 0000000..cca1b03 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/OpenSSL/cdesc-OpenSSL.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/argument_error%21-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/argument_error%21-i.ri new file mode 100644 index 0000000..aed73ad Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/argument_error%21-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/body-i.ri new file mode 100644 index 0000000..acc0ac4 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/body-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" body:ETI"HTTP::Options#body;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I")Explicit request body of the request;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/cdesc-Options.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/cdesc-Options.ri new file mode 100644 index 0000000..12f2060 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/cdesc-Options.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/default_socket_class-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/default_socket_class-c.ri new file mode 100644 index 0000000..98555c8 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/default_socket_class-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/default_ssl_socket_class-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/default_ssl_socket_class-c.ri new file mode 100644 index 0000000..41554b1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/default_ssl_socket_class-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/dup-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/dup-i.ri new file mode 100644 index 0000000..a4bc89b Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/dup-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/follow-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/follow-i.ri new file mode 100644 index 0000000..faae1dd --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/follow-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" follow:ETI"HTTP::Options#follow;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"Follow redirects;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/form-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/form-i.ri new file mode 100644 index 0000000..4d6984d --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/form-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" form:ETI"HTTP::Options#form;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"&Form data to embed in the request;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/headers-i.ri new file mode 100644 index 0000000..8275147 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/headers-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" headers:ETI"HTTP::Options#headers;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"+HTTP headers to include in the request;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/json-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/json-i.ri new file mode 100644 index 0000000..acf4713 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/json-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" json:ETI"HTTP::Options#json;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"&JSON data to embed in the request;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/merge-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/merge-i.ri new file mode 100644 index 0000000..d9a8cb7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/merge-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/new-c.ri new file mode 100644 index 0000000..07a5907 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/params-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/params-i.ri new file mode 100644 index 0000000..22211be --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/params-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" params:ETI"HTTP::Options#params;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"*Query string params to add to the url;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/proxy-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/proxy-i.ri new file mode 100644 index 0000000..533e8ee --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/proxy-i.ri @@ -0,0 +1,3 @@ +U:RDoc::Attr[iI" +proxy:ETI"HTTP::Options#proxy;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I" HTTP proxy to route request;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/response-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/response-i.ri new file mode 100644 index 0000000..93ef028 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/response-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" response:ETI"HTTP::Options#response;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"=How to format the response [:object, :body, :parse_body];T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/socket_class-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/socket_class-i.ri new file mode 100644 index 0000000..503db19 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/socket_class-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"socket_class:ETI"HTTP::Options#socket_class;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"Socket classes;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_context-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_context-i.ri new file mode 100644 index 0000000..78914a9 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_context-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"ssl_context:ETI"HTTP::Options#ssl_context;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"SSL context;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_socket_class-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_socket_class-i.ri new file mode 100644 index 0000000..2f7c97e --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Options/ssl_socket_class-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"ssl_socket_class:ETI"#HTTP::Options#ssl_socket_class;FI"RW;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"Socket classes;T: +@fileI"lib/http/options.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Options;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/to_hash-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/to_hash-i.ri new file mode 100644 index 0000000..685c2b3 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/to_hash-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Options/with_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Options/with_headers-i.ri new file mode 100644 index 0000000..53ad8a2 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Options/with_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Redirector/EndlessRedirectError/cdesc-EndlessRedirectError.ri b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/EndlessRedirectError/cdesc-EndlessRedirectError.ri new file mode 100644 index 0000000..9de5a6f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/EndlessRedirectError/cdesc-EndlessRedirectError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Redirector/TooManyRedirectsError/cdesc-TooManyRedirectsError.ri b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/TooManyRedirectsError/cdesc-TooManyRedirectsError.ri new file mode 100644 index 0000000..b7abf9f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/TooManyRedirectsError/cdesc-TooManyRedirectsError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Redirector/cdesc-Redirector.ri b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/cdesc-Redirector.ri new file mode 100644 index 0000000..7af917e Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Redirector/cdesc-Redirector.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedMethodError/cdesc-UnsupportedMethodError.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedMethodError/cdesc-UnsupportedMethodError.ri new file mode 100644 index 0000000..fd59b6e Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedMethodError/cdesc-UnsupportedMethodError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedSchemeError/cdesc-UnsupportedSchemeError.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedSchemeError/cdesc-UnsupportedSchemeError.ri new file mode 100644 index 0000000..f5115bb Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/UnsupportedSchemeError/cdesc-UnsupportedSchemeError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_body_type_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_body_type_headers-i.ri new file mode 100644 index 0000000..de94afa Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_body_type_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_headers-i.ri new file mode 100644 index 0000000..dad6f1d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/add_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/cdesc-Writer.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/cdesc-Writer.ri new file mode 100644 index 0000000..76637f1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/cdesc-Writer.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/join_headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/join_headers-i.ri new file mode 100644 index 0000000..a1f5362 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/join_headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/new-c.ri new file mode 100644 index 0000000..29002aa Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_body-i.ri new file mode 100644 index 0000000..57174dd Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_body-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_header-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_header-i.ri new file mode 100644 index 0000000..8e3a1e4 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/send_request_header-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/stream-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/stream-i.ri new file mode 100644 index 0000000..21d187e Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/stream-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/validate_body_type%21-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/validate_body_type%21-i.ri new file mode 100644 index 0000000..b50c424 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/Writer/validate_body_type%21-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/__method__-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/__method__-i.ri new file mode 100644 index 0000000..0a91833 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/__method__-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/body-i.ri new file mode 100644 index 0000000..2b3ee7f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/body-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/cdesc-Request.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/cdesc-Request.ri new file mode 100644 index 0000000..fd74ecd Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/cdesc-Request.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/method-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/method-i.ri new file mode 100644 index 0000000..a271a48 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Request/method-i.ri @@ -0,0 +1,3 @@ +U:RDoc::AnyMethod[iI" method:EFI"HTTP::Request#method;FF: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"NThe following method may be removed in two minor versions (0.7.0) or one ;TI"major version (1.0.0);T: +@fileI"lib/http/request.rb;T:0@omit_headings_from_table_of_contents_below000[[I"__method__;To;; [o; +; [I"OThe following alias may be removed in three minor versions (0.8.0) or one ;TI"major version (1.0.0);T; @; 0I"(*);T@FI" Request;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/proxy-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/proxy-i.ri new file mode 100644 index 0000000..376af3d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/proxy-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/scheme-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/scheme-i.ri new file mode 100644 index 0000000..3e58dac --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Request/scheme-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" scheme:ETI"HTTP::Request#scheme;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"EScheme is normalized to be a lowercase symbol e.g. :http, :https;T: +@fileI"lib/http/request.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Request;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/uri-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/uri-i.ri new file mode 100644 index 0000000..568c4d3 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Request/uri-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI"uri:ETI"HTTP::Request#uri;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I"#"Request URI" as per RFC 2616 ;TI":http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html;T: +@fileI"lib/http/request.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Request;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/verb-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/verb-i.ri new file mode 100644 index 0000000..19f2489 --- /dev/null +++ b/.gems/doc/http-0.6.2/ri/HTTP/Request/verb-i.ri @@ -0,0 +1,2 @@ +U:RDoc::Attr[iI" verb:ETI"HTTP::Request#verb;FI"R;T: publico:RDoc::Markup::Document: @parts[o:RDoc::Markup::Paragraph; [I";Method is given as a lowercase symbol e.g. :get, :post;T: +@fileI"lib/http/request.rb;T:0@omit_headings_from_table_of_contents_below0F@I"HTTP::Request;FcRDoc::NormalClass0 \ No newline at end of file diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Request/version-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Request/version-i.ri new file mode 100644 index 0000000..b07c249 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Request/version-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/RequestError/cdesc-RequestError.ri b/.gems/doc/http-0.6.2/ri/HTTP/RequestError/cdesc-RequestError.ri new file mode 100644 index 0000000..e90d8c5 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/RequestError/cdesc-RequestError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/cdesc-Body.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/cdesc-Body.ri new file mode 100644 index 0000000..68cadd2 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/cdesc-Body.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/each-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/each-i.ri new file mode 100644 index 0000000..243a862 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/each-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/new-c.ri new file mode 100644 index 0000000..137cd20 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/readpartial-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/readpartial-i.ri new file mode 100644 index 0000000..71d9f23 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Body/readpartial-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/%3c%3c-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/%3c%3c-i.ri new file mode 100644 index 0000000..fe55d98 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/%3c%3c-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/add-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/add-i.ri new file mode 100644 index 0000000..3cbb28d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/add-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/cdesc-Parser.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/cdesc-Parser.ri new file mode 100644 index 0000000..4177349 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/cdesc-Parser.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/chunk-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/chunk-i.ri new file mode 100644 index 0000000..df76fb8 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/chunk-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/finished%3f-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/finished%3f-i.ri new file mode 100644 index 0000000..4fbb089 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/finished%3f-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers%3f-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers%3f-i.ri new file mode 100644 index 0000000..035ca5c Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers%3f-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers-i.ri new file mode 100644 index 0000000..b812755 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/headers-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/http_version-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/http_version-i.ri new file mode 100644 index 0000000..e5f7cf1 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/http_version-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/new-c.ri new file mode 100644 index 0000000..177c2e7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_body-i.ri new file mode 100644 index 0000000..cb6208e Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_body-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_headers_complete-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_headers_complete-i.ri new file mode 100644 index 0000000..fce782c Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_headers_complete-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_message_complete-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_message_complete-i.ri new file mode 100644 index 0000000..d19fa00 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/on_message_complete-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/reset-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/reset-i.ri new file mode 100644 index 0000000..7e9b52f Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/reset-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/status_code-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/status_code-i.ri new file mode 100644 index 0000000..f98a53e Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/Parser/status_code-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/body-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/body-i.ri new file mode 100644 index 0000000..d06e00d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/body-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/cdesc-Response.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/cdesc-Response.ri new file mode 100644 index 0000000..71decab Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/cdesc-Response.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/charset-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/charset-i.ri new file mode 100644 index 0000000..f737ba2 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/charset-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/code-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/code-i.ri new file mode 100644 index 0000000..205d134 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/code-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/content_type-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/content_type-i.ri new file mode 100644 index 0000000..474f14a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/content_type-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/flush-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/flush-i.ri new file mode 100644 index 0000000..a729461 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/flush-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/inspect-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/inspect-i.ri new file mode 100644 index 0000000..47a9785 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/inspect-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/mime_type-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/mime_type-i.ri new file mode 100644 index 0000000..55bd268 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/mime_type-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/new-c.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/new-c.ri new file mode 100644 index 0000000..9e1b57b Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/new-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/parse-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/parse-i.ri new file mode 100644 index 0000000..db1df9d Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/parse-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/reason-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/reason-i.ri new file mode 100644 index 0000000..d9ad98a Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/reason-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/status-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/status-i.ri new file mode 100644 index 0000000..f961d37 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/status-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/status_code-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/status_code-i.ri new file mode 100644 index 0000000..eb5f4ec Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/status_code-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/stream%21-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/stream%21-i.ri new file mode 100644 index 0000000..eee73ac Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/stream%21-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/to_a-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_a-i.ri new file mode 100644 index 0000000..bf594be Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_a-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/to_s-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_s-i.ri new file mode 100644 index 0000000..69de7ef Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_s-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/to_str-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_str-i.ri new file mode 100644 index 0000000..d74b551 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/to_str-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/Response/uri-i.ri b/.gems/doc/http-0.6.2/ri/HTTP/Response/uri-i.ri new file mode 100644 index 0000000..cf3894c Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/Response/uri-i.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/ResponseError/cdesc-ResponseError.ri b/.gems/doc/http-0.6.2/ri/HTTP/ResponseError/cdesc-ResponseError.ri new file mode 100644 index 0000000..6d9cdac Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/ResponseError/cdesc-ResponseError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/StateError/cdesc-StateError.ri b/.gems/doc/http-0.6.2/ri/HTTP/StateError/cdesc-StateError.ri new file mode 100644 index 0000000..e0b81f0 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/StateError/cdesc-StateError.ri differ diff --git a/.gems/doc/http-0.6.2/ri/HTTP/cdesc-HTTP.ri b/.gems/doc/http-0.6.2/ri/HTTP/cdesc-HTTP.ri new file mode 100644 index 0000000..0e9f222 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/HTTP/cdesc-HTTP.ri differ diff --git a/.gems/doc/http-0.6.2/ri/Object/cdesc-Object.ri b/.gems/doc/http-0.6.2/ri/Object/cdesc-Object.ri new file mode 100644 index 0000000..0cfd326 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/Object/cdesc-Object.ri differ diff --git a/.gems/doc/http-0.6.2/ri/URI/cdesc-URI.ri b/.gems/doc/http-0.6.2/ri/URI/cdesc-URI.ri new file mode 100644 index 0000000..7e905c7 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/URI/cdesc-URI.ri differ diff --git a/.gems/doc/http-0.6.2/ri/URI/decode_www_form-c.ri b/.gems/doc/http-0.6.2/ri/URI/decode_www_form-c.ri new file mode 100644 index 0000000..20fb41c Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/URI/decode_www_form-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/URI/decode_www_form_component-c.ri b/.gems/doc/http-0.6.2/ri/URI/decode_www_form_component-c.ri new file mode 100644 index 0000000..6b51056 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/URI/decode_www_form_component-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/URI/encode_www_form-c.ri b/.gems/doc/http-0.6.2/ri/URI/encode_www_form-c.ri new file mode 100644 index 0000000..3f3bb51 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/URI/encode_www_form-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/URI/encode_www_form_component-c.ri b/.gems/doc/http-0.6.2/ri/URI/encode_www_form_component-c.ri new file mode 100644 index 0000000..af1f095 Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/URI/encode_www_form_component-c.ri differ diff --git a/.gems/doc/http-0.6.2/ri/cache.ri b/.gems/doc/http-0.6.2/ri/cache.ri new file mode 100644 index 0000000..1c3438c Binary files /dev/null and b/.gems/doc/http-0.6.2/ri/cache.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/cdesc-Parser.ri b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/cdesc-Parser.ri new file mode 100644 index 0000000..bab5ea5 Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/cdesc-Parser.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type%3d-c.ri b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type%3d-c.ri new file mode 100644 index 0000000..23ed78b Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type%3d-c.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type-c.ri b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type-c.ri new file mode 100644 index 0000000..88b0b1d Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/Parser/default_header_value_type-c.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/cdesc-HTTP.ri b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/cdesc-HTTP.ri new file mode 100644 index 0000000..2450fd3 Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/HTTP/cdesc-HTTP.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/Object/cdesc-Object.ri b/.gems/doc/http_parser.rb-0.6.0/ri/Object/cdesc-Object.ri new file mode 100644 index 0000000..0f9d4d0 Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/Object/cdesc-Object.ri differ diff --git a/.gems/doc/http_parser.rb-0.6.0/ri/cache.ri b/.gems/doc/http_parser.rb-0.6.0/ri/cache.ri new file mode 100644 index 0000000..63e19db Binary files /dev/null and b/.gems/doc/http_parser.rb-0.6.0/ri/cache.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/cdesc-InstanceMethods.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/cdesc-InstanceMethods.ri new file mode 100644 index 0000000..9ffe66c Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/cdesc-InstanceMethods.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/freeze-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/freeze-i.ri new file mode 100644 index 0000000..a9b769d Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/freeze-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoize-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoize-i.ri new file mode 100644 index 0000000..f259b49 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoize-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoized_method_cache-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoized_method_cache-i.ri new file mode 100644 index 0000000..8972bbc Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/InstanceMethods/memoized_method_cache-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d%3d-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d%3d-i.ri new file mode 100644 index 0000000..1315f4e Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d-i.ri new file mode 100644 index 0000000..000f781 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/%5b%5d-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/cdesc-Memory.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/cdesc-Memory.ri new file mode 100644 index 0000000..5d41cad Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/cdesc-Memory.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/fetch-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/fetch-i.ri new file mode 100644 index 0000000..c667112 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/fetch-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/key%3f-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/key%3f-i.ri new file mode 100644 index 0000000..ccad5c6 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/key%3f-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_dump-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_dump-i.ri new file mode 100644 index 0000000..6ba5c1e Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_dump-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_load-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_load-i.ri new file mode 100644 index 0000000..61b2482 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/marshal_load-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/new-c.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/new-c.ri new file mode 100644 index 0000000..f1aaac0 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/Memory/new-c.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/cdesc-BlockNotAllowedError.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/cdesc-BlockNotAllowedError.ri new file mode 100644 index 0000000..8ad2c7a Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/cdesc-BlockNotAllowedError.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/new-c.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/new-c.ri new file mode 100644 index 0000000..d04b53a Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/BlockNotAllowedError/new-c.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/cdesc-InvalidArityError.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/cdesc-InvalidArityError.ri new file mode 100644 index 0000000..dfe2468 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/cdesc-InvalidArityError.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/new-c.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/new-c.ri new file mode 100644 index 0000000..856c8da Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/InvalidArityError/new-c.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/assert_arity-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/assert_arity-i.ri new file mode 100644 index 0000000..0c8a866 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/assert_arity-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/call-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/call-i.ri new file mode 100644 index 0000000..14539f0 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/call-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/cdesc-MethodBuilder.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/cdesc-MethodBuilder.ri new file mode 100644 index 0000000..005f6c9 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/cdesc-MethodBuilder.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/create_memoized_method-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/create_memoized_method-i.ri new file mode 100644 index 0000000..46c2147 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/create_memoized_method-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/new-c.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/new-c.ri new file mode 100644 index 0000000..53d2002 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/new-c.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/original_method-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/original_method-i.ri new file mode 100644 index 0000000..9d13c19 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/original_method-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/remove_original_method-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/remove_original_method-i.ri new file mode 100644 index 0000000..a63c413 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/remove_original_method-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/set_method_visibility-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/set_method_visibility-i.ri new file mode 100644 index 0000000..07ef4d7 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/set_method_visibility-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/visibility-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/visibility-i.ri new file mode 100644 index 0000000..9162804 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/MethodBuilder/visibility-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/cdesc-ModuleMethods.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/cdesc-ModuleMethods.ri new file mode 100644 index 0000000..dc4273f Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/cdesc-ModuleMethods.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/freezer-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/freezer-i.ri new file mode 100644 index 0000000..a4b8579 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/freezer-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/included-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/included-i.ri new file mode 100644 index 0000000..a9501c2 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/included-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize-i.ri new file mode 100644 index 0000000..523f9e1 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize_method-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize_method-i.ri new file mode 100644 index 0000000..5f22a70 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoize_method-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized%3f-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized%3f-i.ri new file mode 100644 index 0000000..29b673f Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized%3f-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized_methods-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized_methods-i.ri new file mode 100644 index 0000000..13151db Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/memoized_methods-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/unmemoized_instance_method-i.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/unmemoized_instance_method-i.ri new file mode 100644 index 0000000..0ce70de Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/ModuleMethods/unmemoized_instance_method-i.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/cdesc-Memoizable.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/cdesc-Memoizable.ri new file mode 100644 index 0000000..40556b5 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/cdesc-Memoizable.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/Memoizable/included-c.ri b/.gems/doc/memoizable-0.4.2/ri/Memoizable/included-c.ri new file mode 100644 index 0000000..dd41221 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/Memoizable/included-c.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/cache.ri b/.gems/doc/memoizable-0.4.2/ri/cache.ri new file mode 100644 index 0000000..cf4370c Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/cache.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/page-CONTRIBUTING_md.ri b/.gems/doc/memoizable-0.4.2/ri/page-CONTRIBUTING_md.ri new file mode 100644 index 0000000..e8109e3 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/page-CONTRIBUTING_md.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/page-LICENSE_md.ri b/.gems/doc/memoizable-0.4.2/ri/page-LICENSE_md.ri new file mode 100644 index 0000000..27b43fd Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/page-LICENSE_md.ri differ diff --git a/.gems/doc/memoizable-0.4.2/ri/page-README_md.ri b/.gems/doc/memoizable-0.4.2/ri/page-README_md.ri new file mode 100644 index 0000000..e5a5546 Binary files /dev/null and b/.gems/doc/memoizable-0.4.2/ri/page-README_md.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/advance_io-i.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/advance_io-i.ri new file mode 100644 index 0000000..7a0e490 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/advance_io-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/cdesc-CompositeReadIO.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/cdesc-CompositeReadIO.ri new file mode 100644 index 0000000..803b5f7 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/cdesc-CompositeReadIO.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/current_io-i.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/current_io-i.ri new file mode 100644 index 0000000..ef1ad15 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/current_io-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/new-c.ri new file mode 100644 index 0000000..8a1a398 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/read-i.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/read-i.ri new file mode 100644 index 0000000..55299aa Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/read-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/rewind-i.ri b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/rewind-i.ri new file mode 100644 index 0000000..e04cbc8 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/CompositeReadIO/rewind-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/MultipartPost/cdesc-MultipartPost.ri b/.gems/doc/multipart-post-2.0.0/ri/MultipartPost/cdesc-MultipartPost.ri new file mode 100644 index 0000000..5b78310 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/MultipartPost/cdesc-MultipartPost.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Multipartable/cdesc-Multipartable.ri b/.gems/doc/multipart-post-2.0.0/ri/Multipartable/cdesc-Multipartable.ri new file mode 100644 index 0000000..6c4b873 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Multipartable/cdesc-Multipartable.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Multipartable/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/Multipartable/new-c.ri new file mode 100644 index 0000000..d7eb1a0 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Multipartable/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/Multipart/cdesc-Multipart.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/Multipart/cdesc-Multipart.ri new file mode 100644 index 0000000..9710fed Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/Multipart/cdesc-Multipart.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/cdesc-Post.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/cdesc-Post.ri new file mode 100644 index 0000000..ee8d575 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Post/cdesc-Post.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/Multipart/cdesc-Multipart.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/Multipart/cdesc-Multipart.ri new file mode 100644 index 0000000..82f784b Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/Multipart/cdesc-Multipart.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/cdesc-Put.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/cdesc-Put.ri new file mode 100644 index 0000000..a5a693e Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/Put/cdesc-Put.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/cdesc-HTTP.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/cdesc-HTTP.ri new file mode 100644 index 0000000..faf4390 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/HTTP/cdesc-HTTP.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Net/cdesc-Net.ri b/.gems/doc/multipart-post-2.0.0/ri/Net/cdesc-Net.ri new file mode 100644 index 0000000..07d1a00 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Net/cdesc-Net.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/cdesc-EpiloguePart.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/cdesc-EpiloguePart.ri new file mode 100644 index 0000000..61f6e3a Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/cdesc-EpiloguePart.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/new-c.ri new file mode 100644 index 0000000..1ed525c Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/EpiloguePart/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/build_head-i.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/build_head-i.ri new file mode 100644 index 0000000..e0566fd Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/build_head-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/cdesc-FilePart.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/cdesc-FilePart.ri new file mode 100644 index 0000000..11e9054 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/cdesc-FilePart.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/length-i.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/length-i.ri new file mode 100644 index 0000000..9cb856b Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/length-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/new-c.ri new file mode 100644 index 0000000..c546b69 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/FilePart/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/build_part-i.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/build_part-i.ri new file mode 100644 index 0000000..e83c968 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/build_part-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/cdesc-ParamPart.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/cdesc-ParamPart.ri new file mode 100644 index 0000000..f924419 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/cdesc-ParamPart.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/length-i.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/length-i.ri new file mode 100644 index 0000000..0a9ee88 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/length-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/new-c.ri new file mode 100644 index 0000000..585c21b Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/ParamPart/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/Part/cdesc-Part.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/Part/cdesc-Part.ri new file mode 100644 index 0000000..47bcc74 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/Part/cdesc-Part.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/Parts/cdesc-Parts.ri b/.gems/doc/multipart-post-2.0.0/ri/Parts/cdesc-Parts.ri new file mode 100644 index 0000000..724533f Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/Parts/cdesc-Parts.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/cdesc-UploadIO.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/cdesc-UploadIO.ri new file mode 100644 index 0000000..fcc265c Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/cdesc-UploadIO.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/content_type-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/content_type-i.ri new file mode 100644 index 0000000..fda546d Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/content_type-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/convert%21-c.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/convert%21-c.ri new file mode 100644 index 0000000..a988be0 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/convert%21-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/io-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/io-i.ri new file mode 100644 index 0000000..e1ae17d Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/io-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/local_path-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/local_path-i.ri new file mode 100644 index 0000000..276e178 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/local_path-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/method_missing-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/method_missing-i.ri new file mode 100644 index 0000000..13f00bf Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/method_missing-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/new-c.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/new-c.ri new file mode 100644 index 0000000..835e9fc Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/new-c.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/opts-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/opts-i.ri new file mode 100644 index 0000000..ddff43d Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/opts-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/original_filename-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/original_filename-i.ri new file mode 100644 index 0000000..eb63373 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/original_filename-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/UploadIO/respond_to%3f-i.ri b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/respond_to%3f-i.ri new file mode 100644 index 0000000..9f3d27c Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/UploadIO/respond_to%3f-i.ri differ diff --git a/.gems/doc/multipart-post-2.0.0/ri/cache.ri b/.gems/doc/multipart-post-2.0.0/ri/cache.ri new file mode 100644 index 0000000..48dd3d5 Binary files /dev/null and b/.gems/doc/multipart-post-2.0.0/ri/cache.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/BasicObject/cdesc-BasicObject.ri b/.gems/doc/naught-1.0.0/ri/Naught/BasicObject/cdesc-BasicObject.ri new file mode 100644 index 0000000..a1c8165 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/BasicObject/cdesc-BasicObject.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Actual-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Actual-i.ri new file mode 100644 index 0000000..f9d538a Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Actual-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Just-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Just-i.ri new file mode 100644 index 0000000..169ecbb Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Just-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Maybe-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Maybe-i.ri new file mode 100644 index 0000000..2fa3ff8 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Maybe-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Null-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Null-i.ri new file mode 100644 index 0000000..e3d7012 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/Null-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/cdesc-Conversions.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/cdesc-Conversions.ri new file mode 100644 index 0000000..ca18a00 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/cdesc-Conversions.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/Conversions/included-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/included-c.ri new file mode 100644 index 0000000..4b8b8ee Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/Conversions/included-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/builder-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/builder-i.ri new file mode 100644 index 0000000..870697c Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/builder-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/call-i.ri new file mode 100644 index 0000000..2f75c0f Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/cdesc-Command.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/cdesc-Command.ri new file mode 100644 index 0000000..c3a0967 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/cdesc-Command.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/defer-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/defer-i.ri new file mode 100644 index 0000000..d4d0718 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/defer-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/new-c.ri new file mode 100644 index 0000000..1827eaf Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Command/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/call-i.ri new file mode 100644 index 0000000..b39fc10 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/cdesc-DefineExplicitConversions.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/cdesc-DefineExplicitConversions.ri new file mode 100644 index 0000000..b7c3af4 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineExplicitConversions/cdesc-DefineExplicitConversions.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/call-i.ri new file mode 100644 index 0000000..b6237ef Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/cdesc-DefineImplicitConversions.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/cdesc-DefineImplicitConversions.ri new file mode 100644 index 0000000..747a281 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/cdesc-DefineImplicitConversions.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_ary-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_ary-i.ri new file mode 100644 index 0000000..b79ba1c Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_ary-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_str-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_str-i.ri new file mode 100644 index 0000000..3b4d398 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/DefineImplicitConversions/to_str-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/cdesc-Impersonate.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/cdesc-Impersonate.ri new file mode 100644 index 0000000..d8dcb49 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/cdesc-Impersonate.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/new-c.ri new file mode 100644 index 0000000..0666314 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Impersonate/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/call-i.ri new file mode 100644 index 0000000..83be8fc Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/cdesc-Mimic.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/cdesc-Mimic.ri new file mode 100644 index 0000000..953c770 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/cdesc-Mimic.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/class_to_mimic-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/class_to_mimic-i.ri new file mode 100644 index 0000000..7d25211 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/class_to_mimic-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/include_super-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/include_super-i.ri new file mode 100644 index 0000000..a8b1362 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/include_super-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/methods_to_stub-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/methods_to_stub-i.ri new file mode 100644 index 0000000..79ecd4e Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/methods_to_stub-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/new-c.ri new file mode 100644 index 0000000..81dab22 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/root_class_of-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/root_class_of-i.ri new file mode 100644 index 0000000..454e533 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Mimic/root_class_of-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/call-i.ri new file mode 100644 index 0000000..2044c5f Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/cdesc-Pebble.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/cdesc-Pebble.ri new file mode 100644 index 0000000..2def0b5 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/cdesc-Pebble.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/new-c.ri new file mode 100644 index 0000000..9a2af07 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/parse_caller-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/parse_caller-i.ri new file mode 100644 index 0000000..7075c2b Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Pebble/parse_caller-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/call-i.ri new file mode 100644 index 0000000..ed1ec28 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/cdesc-PredicatesReturn.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/cdesc-PredicatesReturn.ri new file mode 100644 index 0000000..acbfa61 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/cdesc-PredicatesReturn.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_method_missing-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_method_missing-i.ri new file mode 100644 index 0000000..9ad3349 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_method_missing-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_predicate_methods-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_predicate_methods-i.ri new file mode 100644 index 0000000..d21bb80 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/define_predicate_methods-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/new-c.ri new file mode 100644 index 0000000..559ed1f Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/PredicatesReturn/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/call-i.ri new file mode 100644 index 0000000..84e461e Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/cdesc-Singleton.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/cdesc-Singleton.ri new file mode 100644 index 0000000..3424771 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/cdesc-Singleton.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/get-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/get-c.ri new file mode 100644 index 0000000..4d8aa1c Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Singleton/get-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/call-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/call-i.ri new file mode 100644 index 0000000..a03cbe0 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/call-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/cdesc-Traceable.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/cdesc-Traceable.ri new file mode 100644 index 0000000..7756ee6 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/cdesc-Traceable.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/new-c.ri new file mode 100644 index 0000000..4798c11 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/Traceable/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/cdesc-Commands.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/cdesc-Commands.ri new file mode 100644 index 0000000..afa0e85 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/Commands/cdesc-Commands.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/apply_operations-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/apply_operations-i.ri new file mode 100644 index 0000000..cd2ba11 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/apply_operations-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/base_class-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/base_class-i.ri new file mode 100644 index 0000000..7c8d335 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/base_class-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/black_hole-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/black_hole-i.ri new file mode 100644 index 0000000..0bd1693 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/black_hole-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/cdesc-NullClassBuilder.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/cdesc-NullClassBuilder.ri new file mode 100644 index 0000000..102190e Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/cdesc-NullClassBuilder.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/class_operations-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/class_operations-i.ri new file mode 100644 index 0000000..78ff8a3 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/class_operations-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/command_name_for_method-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/command_name_for_method-i.ri new file mode 100644 index 0000000..c6cae00 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/command_name_for_method-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customization_module-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customization_module-i.ri new file mode 100644 index 0000000..db73241 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customization_module-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customize-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customize-i.ri new file mode 100644 index 0000000..c8b71f1 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/customize-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/defer-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/defer-i.ri new file mode 100644 index 0000000..d1734fd Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/defer-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_class_methods-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_class_methods-i.ri new file mode 100644 index 0000000..242b731 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_class_methods-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_instance_methods-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_instance_methods-i.ri new file mode 100644 index 0000000..e281603 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_instance_methods-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_methods-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_methods-i.ri new file mode 100644 index 0000000..4283217 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/define_basic_methods-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/generate_class-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/generate_class-i.ri new file mode 100644 index 0000000..c0854e5 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/generate_class-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/get-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/get-c.ri new file mode 100644 index 0000000..ac6c7e5 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/get-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/inspect_proc-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/inspect_proc-i.ri new file mode 100644 index 0000000..011e970 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/inspect_proc-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined%3f-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined%3f-i.ri new file mode 100644 index 0000000..7d25efe Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined%3f-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined-i.ri new file mode 100644 index 0000000..587808d Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/interface_defined-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/method_missing-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/method_missing-i.ri new file mode 100644 index 0000000..eeb096e Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/method_missing-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/new-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/new-c.ri new file mode 100644 index 0000000..4368fa2 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/new-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/null_equivalents-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/null_equivalents-i.ri new file mode 100644 index 0000000..9111393 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/null_equivalents-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/operations-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/operations-i.ri new file mode 100644 index 0000000..5db5bda Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/operations-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to%3f-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to%3f-i.ri new file mode 100644 index 0000000..4c34062 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to%3f-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_any_message-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_any_message-i.ri new file mode 100644 index 0000000..0024826 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_any_message-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_missing%3f-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_missing%3f-i.ri new file mode 100644 index 0000000..a2f28d8 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/respond_to_missing%3f-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method-i.ri new file mode 100644 index 0000000..902ffee Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_nil-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_nil-i.ri new file mode 100644 index 0000000..317a2ce Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_nil-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_self-i.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_self-i.ri new file mode 100644 index 0000000..8f9a1ec Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullClassBuilder/stub_method_returning_self-i.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/NullObjectTag/cdesc-NullObjectTag.ri b/.gems/doc/naught-1.0.0/ri/Naught/NullObjectTag/cdesc-NullObjectTag.ri new file mode 100644 index 0000000..ff11146 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/NullObjectTag/cdesc-NullObjectTag.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/build-c.ri b/.gems/doc/naught-1.0.0/ri/Naught/build-c.ri new file mode 100644 index 0000000..a46b5cb Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/build-c.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/Naught/cdesc-Naught.ri b/.gems/doc/naught-1.0.0/ri/Naught/cdesc-Naught.ri new file mode 100644 index 0000000..1fbfdb3 Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/Naught/cdesc-Naught.ri differ diff --git a/.gems/doc/naught-1.0.0/ri/cache.ri b/.gems/doc/naught-1.0.0/ri/cache.ri new file mode 100644 index 0000000..f671a4d Binary files /dev/null and b/.gems/doc/naught-1.0.0/ri/cache.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/attributes-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/attributes-i.ri new file mode 100644 index 0000000..25c191b Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/attributes-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/cdesc-Header.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/cdesc-Header.ri new file mode 100644 index 0000000..9258bad Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/cdesc-Header.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/decode-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/decode-c.ri new file mode 100644 index 0000000..b2406a7 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/decode-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/default_options-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/default_options-c.ri new file mode 100644 index 0000000..2cf9080 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/default_options-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/encode-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/encode-c.ri new file mode 100644 index 0000000..a23ae70 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/encode-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/escape-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/escape-c.ri new file mode 100644 index 0000000..7ec01d1 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/escape-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/hmac_sha1_signature-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/hmac_sha1_signature-i.ri new file mode 100644 index 0000000..8b1f417 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/hmac_sha1_signature-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/method-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/method-i.ri new file mode 100644 index 0000000..35a2b85 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/method-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/new-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/new-c.ri new file mode 100644 index 0000000..c88fda9 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/new-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_attributes-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_attributes-i.ri new file mode 100644 index 0000000..efbb6ab Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_attributes-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_params-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_params-i.ri new file mode 100644 index 0000000..20c8553 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/normalized_params-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/options-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/options-i.ri new file mode 100644 index 0000000..854dd29 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/options-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/params-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/params-i.ri new file mode 100644 index 0000000..17eeb4e Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/params-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/parse-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/parse-c.ri new file mode 100644 index 0000000..66f11ed Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/parse-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/plaintext_signature-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/plaintext_signature-i.ri new file mode 100644 index 0000000..d148b8e Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/plaintext_signature-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/private_key-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/private_key-i.ri new file mode 100644 index 0000000..2882ca0 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/private_key-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/rsa_sha1_signature-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/rsa_sha1_signature-i.ri new file mode 100644 index 0000000..bff2c52 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/rsa_sha1_signature-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/secret-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/secret-i.ri new file mode 100644 index 0000000..fb86b04 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/secret-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature-i.ri new file mode 100644 index 0000000..d8f8b75 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_base-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_base-i.ri new file mode 100644 index 0000000..4d57156 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_base-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_params-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_params-i.ri new file mode 100644 index 0000000..1e5d854 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signature_params-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signed_attributes-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signed_attributes-i.ri new file mode 100644 index 0000000..7cb5b9a Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/signed_attributes-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/to_s-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/to_s-i.ri new file mode 100644 index 0000000..6fabee1 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/to_s-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/unescape-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/unescape-c.ri new file mode 100644 index 0000000..63a23f6 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/unescape-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/uri_parser-c.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/uri_parser-c.ri new file mode 100644 index 0000000..7a17cb0 Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/uri_parser-c.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url-i.ri new file mode 100644 index 0000000..7e438cf Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url_params-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url_params-i.ri new file mode 100644 index 0000000..b0f9e7c Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/url_params-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/valid%3f-i.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/valid%3f-i.ri new file mode 100644 index 0000000..fe8b82a Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/Header/valid%3f-i.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/cdesc-SimpleOAuth.ri b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/cdesc-SimpleOAuth.ri new file mode 100644 index 0000000..acbb5eb Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/SimpleOAuth/cdesc-SimpleOAuth.ri differ diff --git a/.gems/doc/simple_oauth-0.2.0/ri/cache.ri b/.gems/doc/simple_oauth-0.2.0/ri/cache.ri new file mode 100644 index 0000000..9b6799a Binary files /dev/null and b/.gems/doc/simple_oauth-0.2.0/ri/cache.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/cdesc-SynchronizedDelegator.ri b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/cdesc-SynchronizedDelegator.ri new file mode 100644 index 0000000..4f76576 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/cdesc-SynchronizedDelegator.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/method_missing-i.ri b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/method_missing-i.ri new file mode 100644 index 0000000..cbcc78e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/method_missing-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/new-c.ri new file mode 100644 index 0000000..d42f7ba Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/setup-i.ri b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/setup-i.ri new file mode 100644 index 0000000..38e7b96 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/setup-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/teardown-i.ri b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/teardown-i.ri new file mode 100644 index 0000000..fd22760 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/SynchronizedDelegator/teardown-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Array/cdesc-Array.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Array/cdesc-Array.ri new file mode 100644 index 0000000..9538a3f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Array/cdesc-Array.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d%3d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d%3d-i.ri new file mode 100644 index 0000000..f393809 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d-i.ri new file mode 100644 index 0000000..3340431 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/%5b%5d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/Util/cdesc-Util.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/Util/cdesc-Util.ri new file mode 100644 index 0000000..63ac845 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/Util/cdesc-Util.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/cdesc-Node.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/cdesc-Node.ri new file mode 100644 index 0000000..3873b64 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/cdesc-Node.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/force_aquire_lock-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/force_aquire_lock-i.ri new file mode 100644 index 0000000..7ea14ee Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/force_aquire_lock-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key%3f-i.ri new file mode 100644 index 0000000..1ecc759 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key-i.ri new file mode 100644 index 0000000..cd3e7ff Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/key-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked%3f-i.ri new file mode 100644 index 0000000..3dd8133 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked_hash%3f-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked_hash%3f-c.ri new file mode 100644 index 0000000..9c63d39 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/locked_hash%3f-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/matches%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/matches%3f-i.ri new file mode 100644 index 0000000..40d77d5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/matches%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/new-c.ri new file mode 100644 index 0000000..ed2c2c4 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/pure_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/pure_hash-i.ri new file mode 100644 index 0000000..3760e45 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/pure_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_await_lock-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_await_lock-i.ri new file mode 100644 index 0000000..8c2f584 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_await_lock-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_lock_via_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_lock_via_hash-i.ri new file mode 100644 index 0000000..7dd250e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/try_lock_via_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/unlock_via_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/unlock_via_hash-i.ri new file mode 100644 index 0000000..50639f7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Node/unlock_via_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cas_new_node-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cas_new_node-i.ri new file mode 100644 index 0000000..e0c428b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cas_new_node-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cdesc-Table.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cdesc-Table.ri new file mode 100644 index 0000000..af1598f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/cdesc-Table.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/delete_node_at-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/delete_node_at-i.ri new file mode 100644 index 0000000..0e44ab7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/delete_node_at-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_lock_via_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_lock_via_hash-i.ri new file mode 100644 index 0000000..14376a2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_lock_via_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_to_cas_in_computed-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_to_cas_in_computed-i.ri new file mode 100644 index 0000000..a910b6a Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/Table/try_to_cas_in_computed-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_compute-i.ri new file mode 100644 index 0000000..704b1b2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_get_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_get_and_set-i.ri new file mode 100644 index 0000000..fdb079c Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_get_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_compute_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_compute_if_absent-i.ri new file mode 100644 index 0000000..add2617 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_compute_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_replace-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_replace-i.ri new file mode 100644 index 0000000..d4f26d0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/attempt_internal_replace-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/cdesc-AtomicReferenceCacheBackend.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/cdesc-AtomicReferenceCacheBackend.ri new file mode 100644 index 0000000..feec86e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/cdesc-AtomicReferenceCacheBackend.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/check_for_resize-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/check_for_resize-i.ri new file mode 100644 index 0000000..7c91bc0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/check_for_resize-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/clear-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/clear-i.ri new file mode 100644 index 0000000..4f47c80 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/clear-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute-i.ri new file mode 100644 index 0000000..db1689e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_absent-i.ri new file mode 100644 index 0000000..bf9313d Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_present-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_present-i.ri new file mode 100644 index 0000000..c5af8ec Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/compute_if_present-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete-i.ri new file mode 100644 index 0000000..f8f6c59 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete_pair-i.ri new file mode 100644 index 0000000..fdea750 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/delete_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/each_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/each_pair-i.ri new file mode 100644 index 0000000..881ee97 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/each_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/empty%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/empty%3f-i.ri new file mode 100644 index 0000000..18933b0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/empty%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/find_value_in_node_list-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/find_value_in_node_list-i.ri new file mode 100644 index 0000000..f586de4 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/find_value_in_node_list-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_and_set-i.ri new file mode 100644 index 0000000..5982174 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_or_default-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_or_default-i.ri new file mode 100644 index 0000000..ebec490 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/get_or_default-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_copy-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_copy-i.ri new file mode 100644 index 0000000..4cc28a3 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_copy-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_table-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_table-i.ri new file mode 100644 index 0000000..3e14a19 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/initialize_table-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_compute-i.ri new file mode 100644 index 0000000..9645e97 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_replace-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_replace-i.ri new file mode 100644 index 0000000..4181e98 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/internal_replace-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key%3f-i.ri new file mode 100644 index 0000000..f8c7a59 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key_hash-i.ri new file mode 100644 index 0000000..903ac48 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/key_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/merge_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/merge_pair-i.ri new file mode 100644 index 0000000..7cad86d Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/merge_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/new-c.ri new file mode 100644 index 0000000..3e8ac71 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_if_exists-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_if_exists-i.ri new file mode 100644 index 0000000..f172adc Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_if_exists-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_pair-i.ri new file mode 100644 index 0000000..33cdb57 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/replace_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/size-i.ri new file mode 100644 index 0000000..69d73b0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/table_size_for-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/table_size_for-i.ri new file mode 100644 index 0000000..1c72df2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/table_size_for-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/try_await_lock-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/try_await_lock-i.ri new file mode 100644 index 0000000..587e3aa Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/AtomicReferenceCacheBackend/try_await_lock-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/%5b%5d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/%5b%5d-i.ri new file mode 100644 index 0000000..d651429 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/%5b%5d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/cdesc-Cache.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/cdesc-Cache.ri new file mode 100644 index 0000000..a502e2e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/cdesc-Cache.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_key-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_key-i.ri new file mode 100644 index 0000000..84a5179 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_key-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_value-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_value-i.ri new file mode 100644 index 0000000..1aec99f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/each_value-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/empty%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/empty%3f-i.ri new file mode 100644 index 0000000..a436789 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/empty%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch-i.ri new file mode 100644 index 0000000..06545c2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch_or_store-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch_or_store-i.ri new file mode 100644 index 0000000..9e52eb0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/fetch_or_store-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/get-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/get-i.ri new file mode 100644 index 0000000..87d6da7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/get-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/index-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/index-i.ri new file mode 100644 index 0000000..be490ef Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/index-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/initialize_copy-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/initialize_copy-i.ri new file mode 100644 index 0000000..6d39cd4 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/initialize_copy-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/key-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/key-i.ri new file mode 100644 index 0000000..bcb72be Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/key-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/keys-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/keys-i.ri new file mode 100644 index 0000000..209dc82 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/keys-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_dump-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_dump-i.ri new file mode 100644 index 0000000..fdd2fcc Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_dump-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_load-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_load-i.ri new file mode 100644 index 0000000..1bc489f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/marshal_load-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/new-c.ri new file mode 100644 index 0000000..0378ca5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/populate_from-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/populate_from-i.ri new file mode 100644 index 0000000..05209d0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/populate_from-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/put_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/put_if_absent-i.ri new file mode 100644 index 0000000..9936e81 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/put_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/raise_fetch_no_key-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/raise_fetch_no_key-i.ri new file mode 100644 index 0000000..ccd5bd5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/raise_fetch_no_key-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/size-i.ri new file mode 100644 index 0000000..f800aa1 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/validate_options_hash%21-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/validate_options_hash%21-i.ri new file mode 100644 index 0000000..cdc8aef Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/validate_options_hash%21-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/value%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/value%3f-i.ri new file mode 100644 index 0000000..efbb2be Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/value%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/values-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/values-i.ri new file mode 100644 index 0000000..5bd278e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Cache/values-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Hash/cdesc-Hash.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Hash/cdesc-Hash.ri new file mode 100644 index 0000000..d7f5a76 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Hash/cdesc-Hash.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/%5b%5d%3d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/%5b%5d%3d-i.ri new file mode 100644 index 0000000..1aa3d9e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/cdesc-MriCacheBackend.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/cdesc-MriCacheBackend.ri new file mode 100644 index 0000000..3a3e80a Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/cdesc-MriCacheBackend.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/clear-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/clear-i.ri new file mode 100644 index 0000000..9221be0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/clear-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute-i.ri new file mode 100644 index 0000000..7fcfbc0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_absent-i.ri new file mode 100644 index 0000000..3b02d7f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_present-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_present-i.ri new file mode 100644 index 0000000..2ca485f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/compute_if_present-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete-i.ri new file mode 100644 index 0000000..4af34ad Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete_pair-i.ri new file mode 100644 index 0000000..8524179 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/delete_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/get_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/get_and_set-i.ri new file mode 100644 index 0000000..af0043f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/get_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/merge_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/merge_pair-i.ri new file mode 100644 index 0000000..e71ed56 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/merge_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_if_exists-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_if_exists-i.ri new file mode 100644 index 0000000..0a4630b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_if_exists-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_pair-i.ri new file mode 100644 index 0000000..32ee2e4 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/MriCacheBackend/replace_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d%3d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d%3d-i.ri new file mode 100644 index 0000000..9985cf5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d-i.ri new file mode 100644 index 0000000..c6b071c Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/%5b%5d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_get-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_get-i.ri new file mode 100644 index 0000000..5f14a16 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_get-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_set-i.ri new file mode 100644 index 0000000..5c6dd77 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/cdesc-NonConcurrentCacheBackend.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/cdesc-NonConcurrentCacheBackend.ri new file mode 100644 index 0000000..7a32239 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/cdesc-NonConcurrentCacheBackend.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/clear-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/clear-i.ri new file mode 100644 index 0000000..0e30f16 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/clear-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute-i.ri new file mode 100644 index 0000000..66f76da Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_absent-i.ri new file mode 100644 index 0000000..aef3fef Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_present-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_present-i.ri new file mode 100644 index 0000000..d99dc44 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/compute_if_present-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete-i.ri new file mode 100644 index 0000000..4b03c60 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete_pair-i.ri new file mode 100644 index 0000000..ffebb3b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/delete_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/dupped_backend-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/dupped_backend-i.ri new file mode 100644 index 0000000..41b16fa Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/dupped_backend-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/each_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/each_pair-i.ri new file mode 100644 index 0000000..c892523 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/each_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_and_set-i.ri new file mode 100644 index 0000000..f5aecd2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_or_default-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_or_default-i.ri new file mode 100644 index 0000000..336ad62 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/get_or_default-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/initialize_copy-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/initialize_copy-i.ri new file mode 100644 index 0000000..27eaa11 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/initialize_copy-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/key%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/key%3f-i.ri new file mode 100644 index 0000000..4284d5b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/key%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/merge_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/merge_pair-i.ri new file mode 100644 index 0000000..182eff7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/merge_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/new-c.ri new file mode 100644 index 0000000..fe2bd71 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/pair%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/pair%3f-i.ri new file mode 100644 index 0000000..e8bb15d Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/pair%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_if_exists-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_if_exists-i.ri new file mode 100644 index 0000000..0c41630 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_if_exists-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_pair-i.ri new file mode 100644 index 0000000..ecceeed Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/replace_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/size-i.ri new file mode 100644 index 0000000..9498bd6 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/store_computed_value-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/store_computed_value-i.ri new file mode 100644 index 0000000..397c57a Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/store_computed_value-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/value%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/value%3f-i.ri new file mode 100644 index 0000000..79976a3 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/NonConcurrentCacheBackend/value%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d%3d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d%3d-i.ri new file mode 100644 index 0000000..9ded170 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d%3d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d-i.ri new file mode 100644 index 0000000..8673b62 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/%5b%5d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/cdesc-SynchronizedCacheBackend.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/cdesc-SynchronizedCacheBackend.ri new file mode 100644 index 0000000..6c50324 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/cdesc-SynchronizedCacheBackend.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/clear-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/clear-i.ri new file mode 100644 index 0000000..32abc55 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/clear-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute-i.ri new file mode 100644 index 0000000..7d12302 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_absent-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_absent-i.ri new file mode 100644 index 0000000..77a812e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_absent-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_present-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_present-i.ri new file mode 100644 index 0000000..fde9878 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/compute_if_present-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete-i.ri new file mode 100644 index 0000000..53cea63 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete_pair-i.ri new file mode 100644 index 0000000..57a36f5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/delete_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/dupped_backend-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/dupped_backend-i.ri new file mode 100644 index 0000000..c3cd10e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/dupped_backend-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_and_set-i.ri new file mode 100644 index 0000000..b182ad0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_or_default-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_or_default-i.ri new file mode 100644 index 0000000..281a63e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/get_or_default-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/key%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/key%3f-i.ri new file mode 100644 index 0000000..3f347d0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/key%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/merge_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/merge_pair-i.ri new file mode 100644 index 0000000..a042955 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/merge_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_if_exists-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_if_exists-i.ri new file mode 100644 index 0000000..23ad4b5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_if_exists-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_pair-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_pair-i.ri new file mode 100644 index 0000000..d391193 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/replace_pair-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/size-i.ri new file mode 100644 index 0000000..c21a0d8 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/value%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/value%3f-i.ri new file mode 100644 index 0000000..70a9a92 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/SynchronizedCacheBackend/value%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/add-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/add-i.ri new file mode 100644 index 0000000..0a957cb Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/add-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/cdesc-Adder.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/cdesc-Adder.ri new file mode 100644 index 0000000..8ee6e3e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/cdesc-Adder.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/decrement-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/decrement-i.ri new file mode 100644 index 0000000..2fed5b6 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/decrement-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/increment-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/increment-i.ri new file mode 100644 index 0000000..a0276a1 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/increment-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/reset-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/reset-i.ri new file mode 100644 index 0000000..983b86f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/reset-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/sum-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/sum-i.ri new file mode 100644 index 0000000..cf9a608 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Adder/sum-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cdesc-CheapLockable.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cdesc-CheapLockable.ri new file mode 100644 index 0000000..c579fd7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cdesc-CheapLockable.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_broadcast-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_broadcast-i.ri new file mode 100644 index 0000000..94d73ac Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_broadcast-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_synchronize-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_synchronize-i.ri new file mode 100644 index 0000000..cd456b8 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_synchronize-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_wait-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_wait-i.ri new file mode 100644 index 0000000..8d0cfcf Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/CheapLockable/cheap_wait-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/cdesc-PowerOfTwoTuple.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/cdesc-PowerOfTwoTuple.ri new file mode 100644 index 0000000..3d59d83 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/cdesc-PowerOfTwoTuple.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/hash_to_index-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/hash_to_index-i.ri new file mode 100644 index 0000000..4d6b5d6 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/hash_to_index-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/new-c.ri new file mode 100644 index 0000000..c46df17 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/next_in_size_table-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/next_in_size_table-i.ri new file mode 100644 index 0000000..d3e6a1f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/next_in_size_table-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_get_by_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_get_by_hash-i.ri new file mode 100644 index 0000000..5ea4e3b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_get_by_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_set_by_hash-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_set_by_hash-i.ri new file mode 100644 index 0000000..855cd40 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/PowerOfTwoTuple/volatile_set_by_hash-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cas_computed-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cas_computed-i.ri new file mode 100644 index 0000000..1201622 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cas_computed-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cdesc-Cell.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cdesc-Cell.ri new file mode 100644 index 0000000..ffecb6b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/Cell/cdesc-Cell.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cas_base_computed-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cas_base_computed-i.ri new file mode 100644 index 0000000..d7ed781 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cas_base_computed-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cdesc-Striped64.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cdesc-Striped64.ri new file mode 100644 index 0000000..f212d65 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/cdesc-Striped64.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/expand_table_unless_stale-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/expand_table_unless_stale-i.ri new file mode 100644 index 0000000..7cff004 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/expand_table_unless_stale-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/free%3f-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/free%3f-i.ri new file mode 100644 index 0000000..3b3e91d Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/free%3f-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code%3d-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code%3d-i.ri new file mode 100644 index 0000000..e2daa80 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code%3d-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code-i.ri new file mode 100644 index 0000000..004d4a0 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/hash_code-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/internal_reset-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/internal_reset-i.ri new file mode 100644 index 0000000..98f162f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/internal_reset-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/new-c.ri new file mode 100644 index 0000000..0b98ce9 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/retry_update-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/retry_update-i.ri new file mode 100644 index 0000000..a51c7c1 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/retry_update-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_in_busy-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_in_busy-i.ri new file mode 100644 index 0000000..fcc80af Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_in_busy-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_initialize_cells-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_initialize_cells-i.ri new file mode 100644 index 0000000..d534b64 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_initialize_cells-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_to_install_new_cell-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_to_install_new_cell-i.ri new file mode 100644 index 0000000..b42a622 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Striped64/try_to_install_new_cell-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/attr_volatile-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/attr_volatile-i.ri new file mode 100644 index 0000000..2d47d19 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/attr_volatile-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/cdesc-Volatile.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/cdesc-Volatile.ri new file mode 100644 index 0000000..c3fc9a4 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/Volatile/cdesc-Volatile.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cas-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cas-i.ri new file mode 100644 index 0000000..b80e65b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cas-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cdesc-VolatileTuple.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cdesc-VolatileTuple.ri new file mode 100644 index 0000000..50e6cc9 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/cdesc-VolatileTuple.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/compare_and_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/compare_and_set-i.ri new file mode 100644 index 0000000..34262c5 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/compare_and_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/each-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/each-i.ri new file mode 100644 index 0000000..aa87bec Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/each-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/new-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/new-c.ri new file mode 100644 index 0000000..57be74e Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/new-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/size-i.ri new file mode 100644 index 0000000..e862898 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_get-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_get-i.ri new file mode 100644 index 0000000..b40e35f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_get-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_set-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_set-i.ri new file mode 100644 index 0000000..5a32917 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/VolatileTuple/volatile_set-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/cdesc-XorShiftRandom.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/cdesc-XorShiftRandom.ri new file mode 100644 index 0000000..e8b648b Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/cdesc-XorShiftRandom.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/get-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/get-i.ri new file mode 100644 index 0000000..87d37ed Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/get-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/xorshift-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/xorshift-i.ri new file mode 100644 index 0000000..cb342e7 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/XorShiftRandom/xorshift-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/cdesc-Util.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/cdesc-Util.ri new file mode 100644 index 0000000..eb84657 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/Util/cdesc-Util.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/_mon_initialize-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/_mon_initialize-i.ri new file mode 100644 index 0000000..e5a05d6 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/_mon_initialize-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/allocate-c.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/allocate-c.ri new file mode 100644 index 0000000..36c5abd Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/allocate-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/cdesc-ThreadSafe.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/cdesc-ThreadSafe.ri new file mode 100644 index 0000000..0f8baf6 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/cdesc-ThreadSafe.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/decrement_size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/decrement_size-i.ri new file mode 100644 index 0000000..6a330cb Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/decrement_size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/increment_size-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/increment_size-i.ri new file mode 100644 index 0000000..acb175c Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/increment_size-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/lock_and_clean_up_reverse_forwarders-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/lock_and_clean_up_reverse_forwarders-i.ri new file mode 100644 index 0000000..8c9d6f1 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/lock_and_clean_up_reverse_forwarders-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/rebuild-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/rebuild-i.ri new file mode 100644 index 0000000..e6c6828 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/rebuild-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_bin-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_bin-i.ri new file mode 100644 index 0000000..dba608f Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_bin-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_old_bin-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_old_bin-i.ri new file mode 100644 index 0000000..1fabcab Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/split_old_bin-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/try_in_resize_lock-i.ri b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/try_in_resize_lock-i.ri new file mode 100644 index 0000000..fc0dc3d Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/ThreadSafe/try_in_resize_lock-i.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/cdesc-Threadsafe.ri b/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/cdesc-Threadsafe.ri new file mode 100644 index 0000000..25fae20 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/cdesc-Threadsafe.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/const_missing-c.ri b/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/const_missing-c.ri new file mode 100644 index 0000000..f2f1c15 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/Threadsafe/const_missing-c.ri differ diff --git a/.gems/doc/thread_safe-0.3.4/ri/cache.ri b/.gems/doc/thread_safe-0.3.4/ri/cache.ri new file mode 100644 index 0000000..5620ca2 Binary files /dev/null and b/.gems/doc/thread_safe-0.3.4/ri/cache.ri differ diff --git a/.gems/gems/addressable-2.3.6/CHANGELOG.md b/.gems/gems/addressable-2.3.6/CHANGELOG.md new file mode 100644 index 0000000..67fcb6f --- /dev/null +++ b/.gems/gems/addressable-2.3.6/CHANGELOG.md @@ -0,0 +1,170 @@ +# Addressable 2.3.6 +- normalization drops empty query string +- better handling in template extract for missing values +- template modifier for `'?'` now treated as optional +- fixed issue where character class parameters were modified +- templates can now be tested for equality +- added `:sorted` option to normalization of query strings +- fixed issue with normalization of hosts given in `'example.com.'` form + +# Addressable 2.3.5 +- added Addressable::URI#empty? method +- Addressable::URI#hostname methods now strip square brackets from IPv6 hosts +- compatibility with Net::HTTP in Ruby 2.0.0 +- Addressable::URI#route_from should always give relative URIs + +# Addressable 2.3.4 +- fixed issue with encoding altering its inputs +- query string normalization now leaves ';' characters alone +- FakeFS is detected before attempting to load unicode tables +- additional testing to ensure frozen objects don't cause problems + +# Addressable 2.3.3 +- fixed issue with converting common primitives during template expansion +- fixed port encoding issue +- removed a few warnings +- normalize should now ignore %2B in query strings +- the IDNA logic should now be handled by libidn in Ruby 1.9 +- no template match should now result in nil instead of an empty MatchData +- added license information to gemspec + +# Addressable 2.3.2 +- added Addressable::URI#default_port method +- fixed issue with Marshalling Unicode data on Windows +- improved heuristic parsing to better handle IPv4 addresses + +# Addressable 2.3.1 +- fixed missing unicode data file + +# Addressable 2.3.0 +- updated Addressable::Template to use RFC 6570, level 4 +- fixed compatibility problems with some versions of Ruby +- moved unicode tables into a data file for performance reasons +- removing support for multiple query value notations + +# Addressable 2.2.8 +- fixed issues with dot segment removal code +- form encoding can now handle multiple values per key +- updated development environment + +# Addressable 2.2.7 +- fixed issues related to Addressable::URI#query_values= +- the Addressable::URI.parse method is now polymorphic + +# Addressable 2.2.6 +- changed the way ambiguous paths are handled +- fixed bug with frozen URIs +- https supported in heuristic parsing + +# Addressable 2.2.5 +- 'parsing' a pre-parsed URI object is now a dup operation +- introduced conditional support for libidn +- fixed normalization issue on ampersands in query strings +- added additional tests around handling of query strings + +# Addressable 2.2.4 +- added origin support from draft-ietf-websec-origin-00 +- resolved issue with attempting to navigate below root +- fixed bug with string splitting in query strings + +# Addressable 2.2.3 +- added :flat_array notation for query strings + +# Addressable 2.2.2 +- fixed issue with percent escaping of '+' character in query strings + +# Addressable 2.2.1 +- added support for application/x-www-form-urlencoded. + +# Addressable 2.2.0 +- added site methods +- improved documentation + +# Addressable 2.1.2 +- added HTTP request URI methods +- better handling of Windows file paths +- validation_deferred boolean replaced with defer_validation block +- normalization of percent-encoded paths should now be correct +- fixed issue with constructing URIs with relative paths +- fixed warnings + +# Addressable 2.1.1 +- more type checking changes +- fixed issue with unicode normalization +- added method to find template defaults +- symbolic keys are now allowed in template mappings +- numeric values and symbolic values are now allowed in template mappings + +# Addressable 2.1.0 +- refactored URI template support out into its own class +- removed extract method due to being useless and unreliable +- removed Addressable::URI.expand_template +- removed Addressable::URI#extract_mapping +- added partial template expansion +- fixed minor bugs in the parse and heuristic_parse methods +- fixed incompatibility with Ruby 1.9.1 +- fixed bottleneck in Addressable::URI#hash and Addressable::URI#to_s +- fixed unicode normalization exception +- updated query_values methods to better handle subscript notation +- worked around issue with freezing URIs +- improved specs + +# Addressable 2.0.2 +- fixed issue with URI template expansion +- fixed issue with percent escaping characters 0-15 + +# Addressable 2.0.1 +- fixed issue with query string assignment +- fixed issue with improperly encoded components + +# Addressable 2.0.0 +- the initialize method now takes an options hash as its only parameter +- added query_values method to URI class +- completely replaced IDNA implementation with pure Ruby +- renamed Addressable::ADDRESSABLE_VERSION to Addressable::VERSION +- completely reworked the Rakefile +- changed the behavior of the port method significantly +- Addressable::URI.encode_segment, Addressable::URI.unencode_segment renamed +- documentation is now in YARD format +- more rigorous type checking +- to_str method implemented, implicit conversion to Strings now allowed +- Addressable::URI#omit method added, Addressable::URI#merge method replaced +- updated URI Template code to match v 03 of the draft spec +- added a bunch of new specifications + +# Addressable 1.0.4 +- switched to using RSpec's pending system for specs that rely on IDN +- fixed issue with creating URIs with paths that are not prefixed with '/' + +# Addressable 1.0.3 +- implemented a hash method + +# Addressable 1.0.2 +- fixed minor bug with the extract_mapping method + +# Addressable 1.0.1 +- fixed minor bug with the extract_mapping method + +# Addressable 1.0.0 +- heuristic parse method added +- parsing is slightly more strict +- replaced to_h with to_hash +- fixed routing methods +- improved specifications +- improved heckle rake task +- no surviving heckle mutations + +# Addressable 0.1.2 +- improved normalization +- fixed bug in joining algorithm +- updated specifications + +# Addressable 0.1.1 +- updated documentation +- added URI Template variable extraction + +# Addressable 0.1.0 +- initial release +- implementation based on RFC 3986, 3987 +- support for IRIs via libidn +- support for the URI Template draft spec diff --git a/.gems/gems/addressable-2.3.6/Gemfile b/.gems/gems/addressable-2.3.6/Gemfile new file mode 100644 index 0000000..6258640 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/Gemfile @@ -0,0 +1,28 @@ +source 'https://rubygems.org' + +group :development do + gem 'launchy' + gem 'yard' + gem 'redcarpet', :platform => :mri_19 + gem 'rubyforge' +end + +group :test, :development do + gem 'rake', '>= 0.7.3' + gem 'rspec', '>= 2.9.0' + gem 'coveralls', :require => false +end + +gem 'idn', :platform => :mri_18 +gem 'idn-ruby', :platform => :mri_19 + +platforms :mri_18 do + gem 'mime-types', '~> 1.25' +end + +platforms :rbx do + gem 'rubysl', '~> 2.0' + gem 'rubinius-coverage' +end + +gemspec diff --git a/.gems/gems/addressable-2.3.6/LICENSE.txt b/.gems/gems/addressable-2.3.6/LICENSE.txt new file mode 100644 index 0000000..ef51da2 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/.gems/gems/addressable-2.3.6/README.md b/.gems/gems/addressable-2.3.6/README.md new file mode 100644 index 0000000..9381a67 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/README.md @@ -0,0 +1,102 @@ +# Addressable + +
+
Homepage
addressable.rubyforge.org
+
Author
Bob Aman
+
Copyright
Copyright © 2006-2013 Bob Aman
+
License
Apache 2.0
+
+ +[![Gem Version](https://badge.fury.io/rb/addressable.png)][gem] +[![Build Status](https://secure.travis-ci.org/sporkmonger/addressable.png?branch=master)][travis] +[![Dependency Status](https://gemnasium.com/sporkmonger/addressable.png?travis)][gemnasium] +[![Coverage Status](https://coveralls.io/repos/sporkmonger/addressable/badge.png?branch=master)][coveralls] + +[gem]: https://rubygems.org/gems/addressable +[travis]: http://travis-ci.org/sporkmonger/addressable +[gemnasium]: https://gemnasium.com/sporkmonger/addressable +[coveralls]: https://coveralls.io/r/sporkmonger/addressable + +# Description + +Addressable is a replacement for the URI implementation that is part of +Ruby's standard library. It more closely conforms to RFC 3986, RFC 3987, and +RFC 6570 (level 4), providing support for IRIs and URI templates. + +# Reference + +- {Addressable::URI} +- {Addressable::Template} + +# Example usage + +```ruby +require "addressable/uri" + +uri = Addressable::URI.parse("http://example.com/path/to/resource/") +uri.scheme +#=> "http" +uri.host +#=> "example.com" +uri.path +#=> "/path/to/resource/" + +uri = Addressable::URI.parse("http://www.詹姆斯.com/") +uri.normalize +#=> # +``` + + +# URI Templates + +For more details, see [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.txt). + + +```ruby + +require "addressable/template" + +template = Addressable::Template.new("http://example.com/{?query*}/") +template.expand({ + "query" => { + 'foo' => 'bar', + 'color' => 'red' + } +}) +#=> # + +template = Addressable::Template.new("http://example.com/{?one,two,three}/") +template.partial_expand({"one" => "1", "three" => 3}).pattern +#=> "http://example.com/?one=1{&two}&three=3" + +template = Addressable::Template.new( + "http://{host}{/segments}/{?one,two,bogus}{#fragment}" +) +uri = Addressable::URI.parse( + "http://example.com/a/b/c/?one=1&two=2#foo" +) +template.extract(uri) +#=> +# { +# "host" => "example.com", +# "segments" => ["a", "b", "c"], +# "one" => "1", +# "two" => "2", +# "fragment" => "foo" +# } +``` + +# Install + +```console +$ sudo gem install addressable +``` + +You may optionally turn on native IDN support by installing libidn and the +idn gem: + +```console +$ sudo apt-get install idn # Debian/Ubuntu +$ sudo brew install libidn # OS X +$ sudo gem install idn-ruby +``` diff --git a/.gems/gems/addressable-2.3.6/Rakefile b/.gems/gems/addressable-2.3.6/Rakefile new file mode 100644 index 0000000..46d3e82 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/Rakefile @@ -0,0 +1,37 @@ +require 'rubygems' +require 'rake' + +require File.join(File.dirname(__FILE__), 'lib', 'addressable', 'version') + +PKG_DISPLAY_NAME = 'Addressable' +PKG_NAME = PKG_DISPLAY_NAME.downcase +PKG_VERSION = Addressable::VERSION::STRING +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +RUBY_FORGE_PROJECT = PKG_NAME +RUBY_FORGE_USER = "sporkmonger" +RUBY_FORGE_PATH = "/var/www/gforge-projects/#{RUBY_FORGE_PROJECT}" +RUBY_FORGE_URL = "http://#{RUBY_FORGE_PROJECT}.rubyforge.org/" + +PKG_SUMMARY = "URI Implementation" +PKG_DESCRIPTION = <<-TEXT +Addressable is a replacement for the URI implementation that is part of +Ruby's standard library. It more closely conforms to the relevant RFCs and +adds support for IRIs and URI templates. +TEXT + +PKG_FILES = FileList[ + "lib/**/*", "spec/**/*", "vendor/**/*", "data/**/*", + "tasks/**/*", "website/**/*", + "[A-Z]*", "Rakefile" +].exclude(/database\.yml/).exclude(/Gemfile\.lock/).exclude(/[_\.]git$/) + +RCOV_ENABLED = (RUBY_PLATFORM != "java" && RUBY_VERSION =~ /^1\.8/) +task :default => "spec" + +WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false +SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS']) + +Dir['tasks/**/*.rake'].each { |rake| load rake } diff --git a/.gems/gems/addressable-2.3.6/data/unicode.data b/.gems/gems/addressable-2.3.6/data/unicode.data new file mode 100644 index 0000000..cdfc224 Binary files /dev/null and b/.gems/gems/addressable-2.3.6/data/unicode.data differ diff --git a/.gems/gems/addressable-2.3.6/lib/addressable/idna.rb b/.gems/gems/addressable-2.3.6/lib/addressable/idna.rb new file mode 100644 index 0000000..2ceb756 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/lib/addressable/idna.rb @@ -0,0 +1,25 @@ +# encoding:utf-8 +#-- +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +begin + require "addressable/idna/native" +rescue LoadError + # libidn or the idn gem was not available, fall back on a pure-Ruby + # implementation... + require "addressable/idna/pure" +end diff --git a/.gems/gems/addressable-2.3.6/lib/addressable/idna/native.rb b/.gems/gems/addressable-2.3.6/lib/addressable/idna/native.rb new file mode 100644 index 0000000..cbf810c --- /dev/null +++ b/.gems/gems/addressable-2.3.6/lib/addressable/idna/native.rb @@ -0,0 +1,43 @@ +# encoding:utf-8 +#-- +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +require "idn" + +module Addressable + module IDNA + def self.punycode_encode(value) + IDN::Punycode.encode(value) + end + + def self.punycode_decode(value) + IDN::Punycode.decode(value) + end + + def self.unicode_normalize_kc(value) + IDN::Stringprep.nfkc_normalize(value) + end + + def self.to_ascii(value) + IDN::Idna.toASCII(value) + end + + def self.to_unicode(value) + IDN::Idna.toUnicode(value) + end + end +end diff --git a/.gems/gems/addressable-2.3.6/lib/addressable/idna/pure.rb b/.gems/gems/addressable-2.3.6/lib/addressable/idna/pure.rb new file mode 100644 index 0000000..5780199 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/lib/addressable/idna/pure.rb @@ -0,0 +1,669 @@ +# encoding:utf-8 +#-- +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +module Addressable + module IDNA + # This module is loosely based on idn_actionmailer by Mick Staugaard, + # the unicode library by Yoshida Masato, and the punycode implementation + # by Kazuhiro Nishiyama. Most of the code was copied verbatim, but + # some reformatting was done, and some translation from C was done. + # + # Without their code to work from as a base, we'd all still be relying + # on the presence of libidn. Which nobody ever seems to have installed. + # + # Original sources: + # http://github.com/staugaard/idn_actionmailer + # http://www.yoshidam.net/Ruby.html#unicode + # http://rubyforge.org/frs/?group_id=2550 + + + UNICODE_TABLE = File.expand_path( + File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data') + ) + + ACE_PREFIX = "xn--" + + UTF8_REGEX = /\A(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*\z/mnx + + UTF8_REGEX_MULTIBYTE = /(?: + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )/mnx + + # :startdoc: + + # Converts from a Unicode internationalized domain name to an ASCII + # domain name as described in RFC 3490. + def self.to_ascii(input) + input = input.dup + if input.respond_to?(:force_encoding) + input.force_encoding(Encoding::ASCII_8BIT) + end + if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE + parts = unicode_downcase(input).split('.') + parts.map! do |part| + if part.respond_to?(:force_encoding) + part.force_encoding(Encoding::ASCII_8BIT) + end + if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE + ACE_PREFIX + punycode_encode(unicode_normalize_kc(part)) + else + part + end + end + parts.join('.') + else + input + end + end + + # Converts from an ASCII domain name to a Unicode internationalized + # domain name as described in RFC 3490. + def self.to_unicode(input) + parts = input.split('.') + parts.map! do |part| + if part =~ /^#{ACE_PREFIX}/ + punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1]) + else + part + end + end + output = parts.join('.') + if output.respond_to?(:force_encoding) + output.force_encoding(Encoding::UTF_8) + end + output + end + + # Unicode normalization form KC. + def self.unicode_normalize_kc(input) + input = input.to_s unless input.is_a?(String) + unpacked = input.unpack("U*") + unpacked = + unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked))) + return unpacked.pack("U*") + end + + ## + # Unicode aware downcase method. + # + # @api private + # @param [String] input + # The input string. + # @return [String] The downcased result. + def self.unicode_downcase(input) + unpacked = input.unpack("U*") + unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) } + return unpacked.pack("U*") + end + (class <= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT && + ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT + # Hangul L + V + return HANGUL_SBASE + ( + (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE) + ) * HANGUL_TCOUNT + elsif ch_one >= HANGUL_SBASE && + ch_one < HANGUL_SBASE + HANGUL_SCOUNT && + (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 && + ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT + # Hangul LV + T + return ch_one + (ch_two - HANGUL_TBASE) + end + + p = [] + ucs4_to_utf8 = lambda do |ch| + # For some reason, rcov likes to drop BUS errors here. + if ch < 128 + p << ch + elsif ch < 2048 + p << (ch >> 6 | 192) + p << (ch & 63 | 128) + elsif ch < 0x10000 + p << (ch >> 12 | 224) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x200000 + p << (ch >> 18 | 240) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x4000000 + p << (ch >> 24 | 248) + p << (ch >> 18 & 63 | 128) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x80000000 + p << (ch >> 30 | 252) + p << (ch >> 24 & 63 | 128) + p << (ch >> 18 & 63 | 128) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + end + end + + ucs4_to_utf8.call(ch_one) + ucs4_to_utf8.call(ch_two) + + return lookup_unicode_composition(p) + end + (class < cc + unpacked[i] = last + unpacked[i-1] = ch + i -= 1 if i > 1 + else + i += 1 + end + end + return unpacked + end + (class <= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT + l, v, t = unicode_decompose_hangul(cp) + unpacked_result << l + unpacked_result << v if v + unpacked_result << t if t + else + dc = lookup_unicode_compatibility(cp) + unless dc + unpacked_result << cp + else + unpacked_result.concat(unicode_decompose(dc.unpack("U*"))) + end + end + end + return unpacked_result + end + (class <= HANGUL_SCOUNT + l = codepoint + v = t = nil + return l, v, t + end + l = HANGUL_LBASE + sindex / HANGUL_NCOUNT + v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT + t = HANGUL_TBASE + sindex % HANGUL_TCOUNT + if t == HANGUL_TBASE + t = nil + end + return l, v, t + end + (class <?" + + "@ABCDEFGHIJKLMNO" + + "PQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmno" + + "pqrstuvwxyz{|}~\n" + + # Input is invalid. + class PunycodeBadInput < StandardError; end + # Output would exceed the space provided. + class PunycodeBigOutput < StandardError; end + # Input needs wider integers to process. + class PunycodeOverflow < StandardError; end + + def self.punycode_encode(unicode) + input = unicode.unpack("U*") + output = [0] * (ACE_MAX_LENGTH + 1) + input_length = input.size + output_length = [ACE_MAX_LENGTH] + + # Initialize the state + n = PUNYCODE_INITIAL_N + delta = out = 0 + max_out = output_length[0] + bias = PUNYCODE_INITIAL_BIAS + + # Handle the basic code points: + input_length.times do |j| + if punycode_basic?(input[j]) + if max_out - out < 2 + raise PunycodeBigOutput, + "Output would exceed the space provided." + end + output[out] = input[j] + out += 1 + end + end + + h = b = out + + # h is the number of code points that have been handled, b is the + # number of basic code points, and out is the number of characters + # that have been output. + + if b > 0 + output[out] = PUNYCODE_DELIMITER + out += 1 + end + + # Main encoding loop: + + while h < input_length + # All non-basic code points < n have been + # handled already. Find the next larger one: + + m = PUNYCODE_MAXINT + input_length.times do |j| + m = input[j] if (n...m) === input[j] + end + + # Increase delta enough to advance the decoder's + # state to , but guard against overflow: + + if m - n > (PUNYCODE_MAXINT - delta) / (h + 1) + raise PunycodeOverflow, "Input needs wider integers to process." + end + delta += (m - n) * (h + 1) + n = m + + input_length.times do |j| + # Punycode does not need to check whether input[j] is basic: + if input[j] < n + delta += 1 + if delta == 0 + raise PunycodeOverflow, + "Input needs wider integers to process." + end + end + + if input[j] == n + # Represent delta as a generalized variable-length integer: + + q = delta; k = PUNYCODE_BASE + while true + if out >= max_out + raise PunycodeBigOutput, + "Output would exceed the space provided." + end + t = ( + if k <= bias + PUNYCODE_TMIN + elsif k >= bias + PUNYCODE_TMAX + PUNYCODE_TMAX + else + k - bias + end + ) + break if q < t + output[out] = + punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t)) + out += 1 + q = (q - t) / (PUNYCODE_BASE - t) + k += PUNYCODE_BASE + end + + output[out] = punycode_encode_digit(q) + out += 1 + bias = punycode_adapt(delta, h + 1, h == b) + delta = 0 + h += 1 + end + end + + delta += 1 + n += 1 + end + + output_length[0] = out + + outlen = out + outlen.times do |j| + c = output[j] + unless c >= 0 && c <= 127 + raise Exception, "Invalid output char." + end + unless PUNYCODE_PRINT_ASCII[c] + raise PunycodeBadInput, "Input is invalid." + end + end + + output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "") + end + (class <= 0 && c <= 127 + raise PunycodeBadInput, "Input is invalid." + end + input.push(c) + end + + input_length = input.length + output_length = [UNICODE_MAX_LENGTH] + + # Initialize the state + n = PUNYCODE_INITIAL_N + + out = i = 0 + max_out = output_length[0] + bias = PUNYCODE_INITIAL_BIAS + + # Handle the basic code points: Let b be the number of input code + # points before the last delimiter, or 0 if there is none, then + # copy the first b code points to the output. + + b = 0 + input_length.times do |j| + b = j if punycode_delimiter?(input[j]) + end + if b > max_out + raise PunycodeBigOutput, "Output would exceed the space provided." + end + + b.times do |j| + unless punycode_basic?(input[j]) + raise PunycodeBadInput, "Input is invalid." + end + output[out] = input[j] + out+=1 + end + + # Main decoding loop: Start just after the last delimiter if any + # basic code points were copied; start at the beginning otherwise. + + in_ = b > 0 ? b + 1 : 0 + while in_ < input_length + + # in_ is the index of the next character to be consumed, and + # out is the number of code points in the output array. + + # Decode a generalized variable-length integer into delta, + # which gets added to i. The overflow checking is easier + # if we increase i as we go, then subtract off its starting + # value at the end to obtain delta. + + oldi = i; w = 1; k = PUNYCODE_BASE + while true + if in_ >= input_length + raise PunycodeBadInput, "Input is invalid." + end + digit = punycode_decode_digit(input[in_]) + in_+=1 + if digit >= PUNYCODE_BASE + raise PunycodeBadInput, "Input is invalid." + end + if digit > (PUNYCODE_MAXINT - i) / w + raise PunycodeOverflow, "Input needs wider integers to process." + end + i += digit * w + t = ( + if k <= bias + PUNYCODE_TMIN + elsif k >= bias + PUNYCODE_TMAX + PUNYCODE_TMAX + else + k - bias + end + ) + break if digit < t + if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t) + raise PunycodeOverflow, "Input needs wider integers to process." + end + w *= PUNYCODE_BASE - t + k += PUNYCODE_BASE + end + + bias = punycode_adapt(i - oldi, out + 1, oldi == 0) + + # I was supposed to wrap around from out + 1 to 0, + # incrementing n each time, so we'll fix that now: + + if i / (out + 1) > PUNYCODE_MAXINT - n + raise PunycodeOverflow, "Input needs wider integers to process." + end + n += i / (out + 1) + i %= out + 1 + + # Insert n at position i of the output: + + # not needed for Punycode: + # raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base + if out >= max_out + raise PunycodeBigOutput, "Output would exceed the space provided." + end + + #memmove(output + i + 1, output + i, (out - i) * sizeof *output) + output[i + 1, out - i] = output[i, out - i] + output[i] = n + i += 1 + + out += 1 + end + + output_length[0] = out + + output.pack("U*") + end + (class <> 1 + # delta >> 1 is a faster way of doing delta / 2 + delta += delta / numpoints + difference = PUNYCODE_BASE - PUNYCODE_TMIN + + k = 0 + while delta > (difference * PUNYCODE_TMAX) / 2 + delta /= difference + k += PUNYCODE_BASE + end + + k + (difference + 1) * delta / (delta + PUNYCODE_SKEW) + end + (class < '?', + '/' => '/', + '#' => '#', + '.' => '.', + ';' => ';', + '&' => '&' + } + JOINERS = { + '?' => '&', + '.' => '.', + ';' => ';', + '&' => '&', + '/' => '/' + } + + ## + # Raised if an invalid template value is supplied. + class InvalidTemplateValueError < StandardError + end + + ## + # Raised if an invalid template operator is used in a pattern. + class InvalidTemplateOperatorError < StandardError + end + + ## + # Raised if an invalid template operator is used in a pattern. + class TemplateOperatorAbortedError < StandardError + end + + ## + # This class represents the data that is extracted when a Template + # is matched against a URI. + class MatchData + ## + # Creates a new MatchData object. + # MatchData objects should never be instantiated directly. + # + # @param [Addressable::URI] uri + # The URI that the template was matched against. + def initialize(uri, template, mapping) + @uri = uri.dup.freeze + @template = template + @mapping = mapping.dup.freeze + end + + ## + # @return [Addressable::URI] + # The URI that the Template was matched against. + attr_reader :uri + + ## + # @return [Addressable::Template] + # The Template used for the match. + attr_reader :template + + ## + # @return [Hash] + # The mapping that resulted from the match. + # Note that this mapping does not include keys or values for + # variables that appear in the Template, but are not present + # in the URI. + attr_reader :mapping + + ## + # @return [Array] + # The list of variables that were present in the Template. + # Note that this list will include variables which do not appear + # in the mapping because they were not present in URI. + def variables + self.template.variables + end + alias_method :keys, :variables + alias_method :names, :variables + + ## + # @return [Array] + # The list of values that were captured by the Template. + # Note that this list will include nils for any variables which + # were in the Template, but did not appear in the URI. + def values + @values ||= self.variables.inject([]) do |accu, key| + accu << self.mapping[key] + accu + end + end + alias_method :captures, :values + + ## + # Accesses captured values by name or by index. + # + # @param [String, Symbol, Fixnum] key + # Capture index or name. Note that when accessing by with index + # of 0, the full URI will be returned. The intention is to mimic + # the ::MatchData#[] behavior. + # + # @param [#to_int, nil] len + # If provided, an array of values will be returend with the given + # parameter used as length. + # + # @return [Array, String, nil] + # The captured value corresponding to the index or name. If the + # value was not provided or the key is unknown, nil will be + # returned. + # + # If the second parameter is provided, an array of that length will + # be returned instead. + def [](key, len = nil) + if len + to_a[key, len] + elsif String === key or Symbol === key + mapping[key.to_s] + else + to_a[key] + end + end + + ## + # @return [Array] + # Array with the matched URI as first element followed by the captured + # values. + def to_a + [to_s, *values] + end + + ## + # @return [String] + # The matched URI as String. + def to_s + uri.to_s + end + alias_method :string, :to_s + + # Returns multiple captured values at once. + # + # @param [String, Symbol, Fixnum] *indexes + # Indices of the captures to be returned + # + # @return [Array] + # Values corresponding to given indices. + # + # @see Addressable::Template::MatchData#[] + def values_at(*indexes) + indexes.map { |i| self[i] } + end + + ## + # Returns a String representation of the MatchData's state. + # + # @return [String] The MatchData's state, as a String. + def inspect + sprintf("#<%s:%#0x RESULT:%s>", + self.class.to_s, self.object_id, self.mapping.inspect) + end + + ## + # Dummy method for code expecting a ::MatchData instance + # + # @return [String] An empty string. + def pre_match + "" + end + alias_method :post_match, :pre_match + end + + ## + # Creates a new Addressable::Template object. + # + # @param [#to_str] pattern The URI Template pattern. + # + # @return [Addressable::Template] The initialized Template object. + def initialize(pattern) + if !pattern.respond_to?(:to_str) + raise TypeError, "Can't convert #{pattern.class} into String." + end + @pattern = pattern.to_str.freeze + end + + ## + # @return [String] The Template object's pattern. + attr_reader :pattern + + ## + # Returns a String representation of the Template object's state. + # + # @return [String] The Template object's state, as a String. + def inspect + sprintf("#<%s:%#0x PATTERN:%s>", + self.class.to_s, self.object_id, self.pattern) + end + + ## + # Returns true if the Template objects are equal. This method + # does NOT normalize either Template before doing the comparison. + # + # @param [Object] template The Template to compare. + # + # @return [TrueClass, FalseClass] + # true if the Templates are equivalent, false + # otherwise. + def ==(template) + return false unless template.kind_of?(Template) + return self.pattern == template.pattern + end + + ## + # Addressable::Template makes no distinction between `==` and `eql?`. + # + # @see #== + alias_method :eql?, :== + + ## + # Extracts a mapping from the URI using a URI Template pattern. + # + # @param [Addressable::URI, #to_str] uri + # The URI to extract from. + # + # @param [#restore, #match] processor + # A template processor object may optionally be supplied. + # + # The object should respond to either the restore or + # match messages or both. The restore method should + # take two parameters: `[String] name` and `[String] value`. + # The restore method should reverse any transformations that + # have been performed on the value to ensure a valid URI. + # The match method should take a single + # parameter: `[String] name`. The match method should return + # a String containing a regular expression capture group for + # matching on that particular variable. The default value is `".*?"`. + # The match method has no effect on multivariate operator + # expansions. + # + # @return [Hash, NilClass] + # The Hash mapping that was extracted from the URI, or + # nil if the URI didn't match the template. + # + # @example + # class ExampleProcessor + # def self.restore(name, value) + # return value.gsub(/\+/, " ") if name == "query" + # return value + # end + # + # def self.match(name) + # return ".*?" if name == "first" + # return ".*" + # end + # end + # + # uri = Addressable::URI.parse( + # "http://example.com/search/an+example+search+query/" + # ) + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).extract(uri, ExampleProcessor) + # #=> {"query" => "an example search query"} + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # Addressable::Template.new( + # "http://example.com/{first}/{second}/" + # ).extract(uri, ExampleProcessor) + # #=> {"first" => "a", "second" => "b/c"} + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # Addressable::Template.new( + # "http://example.com/{first}/{-list|/|second}/" + # ).extract(uri) + # #=> {"first" => "a", "second" => ["b", "c"]} + def extract(uri, processor=nil) + match_data = self.match(uri, processor) + return (match_data ? match_data.mapping : nil) + end + + ## + # Extracts match data from the URI using a URI Template pattern. + # + # @param [Addressable::URI, #to_str] uri + # The URI to extract from. + # + # @param [#restore, #match] processor + # A template processor object may optionally be supplied. + # + # The object should respond to either the restore or + # match messages or both. The restore method should + # take two parameters: `[String] name` and `[String] value`. + # The restore method should reverse any transformations that + # have been performed on the value to ensure a valid URI. + # The match method should take a single + # parameter: `[String] name`. The match method should return + # a String containing a regular expression capture group for + # matching on that particular variable. The default value is `".*?"`. + # The match method has no effect on multivariate operator + # expansions. + # + # @return [Hash, NilClass] + # The Hash mapping that was extracted from the URI, or + # nil if the URI didn't match the template. + # + # @example + # class ExampleProcessor + # def self.restore(name, value) + # return value.gsub(/\+/, " ") if name == "query" + # return value + # end + # + # def self.match(name) + # return ".*?" if name == "first" + # return ".*" + # end + # end + # + # uri = Addressable::URI.parse( + # "http://example.com/search/an+example+search+query/" + # ) + # match = Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).match(uri, ExampleProcessor) + # match.variables + # #=> ["query"] + # match.captures + # #=> ["an example search query"] + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # match = Addressable::Template.new( + # "http://example.com/{first}/{+second}/" + # ).match(uri, ExampleProcessor) + # match.variables + # #=> ["first", "second"] + # match.captures + # #=> ["a", "b/c"] + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # match = Addressable::Template.new( + # "http://example.com/{first}{/second*}/" + # ).match(uri) + # match.variables + # #=> ["first", "second"] + # match.captures + # #=> ["a", ["b", "c"]] + def match(uri, processor=nil) + uri = Addressable::URI.parse(uri) + mapping = {} + + # First, we need to process the pattern, and extract the values. + expansions, expansion_regexp = + parse_template_pattern(pattern, processor) + + return nil unless uri.to_str.match(expansion_regexp) + unparsed_values = uri.to_str.scan(expansion_regexp).flatten + + if uri.to_str == pattern + return Addressable::Template::MatchData.new(uri, self, mapping) + elsif expansions.size > 0 + index = 0 + expansions.each do |expansion| + _, operator, varlist = *expansion.match(EXPRESSION) + varlist.split(',').each do |varspec| + _, name, modifier = *varspec.match(VARSPEC) + mapping[name] ||= nil + case operator + when nil, '+', '#', '/', '.' + unparsed_value = unparsed_values[index] + name = varspec[VARSPEC, 1] + value = unparsed_value + value = value.split(JOINERS[operator]) if value && modifier == '*' + when ';', '?', '&' + if modifier == '*' + if unparsed_values[index] + value = unparsed_values[index].split(JOINERS[operator]) + value = value.inject({}) do |acc, v| + key, val = v.split('=') + val = "" if val.nil? + acc[key] = val + acc + end + end + else + if (unparsed_values[index]) + name, value = unparsed_values[index].split('=') + value = "" if value.nil? + end + end + end + if processor != nil && processor.respond_to?(:restore) + value = processor.restore(name, value) + end + if processor == nil + if value.is_a?(Hash) + value = value.inject({}){|acc, (k, v)| + acc[Addressable::URI.unencode_component(k)] = + Addressable::URI.unencode_component(v) + acc + } + elsif value.is_a?(Array) + value = value.map{|v| Addressable::URI.unencode_component(v) } + else + value = Addressable::URI.unencode_component(value) + end + end + if !mapping.has_key?(name) || mapping[name].nil? + # Doesn't exist, set to value (even if value is nil) + mapping[name] = value + end + index = index + 1 + end + end + return Addressable::Template::MatchData.new(uri, self, mapping) + else + return nil + end + end + + ## + # Expands a URI template into another URI template. + # + # @param [Hash] mapping The mapping that corresponds to the pattern. + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError + # exception will be raised if the value is invalid. The transform + # method should return the transformed variable value as a String. + # If a transform method is used, the value will not be percent + # encoded automatically. Unicode normalization will be performed both + # before and after sending the value to the transform method. + # + # @return [Addressable::Template] The partially expanded URI template. + # + # @example + # Addressable::Template.new( + # "http://example.com/{one}/{two}/" + # ).partial_expand({"one" => "1"}).pattern + # #=> "http://example.com/1/{two}/" + # + # Addressable::Template.new( + # "http://example.com/{?one,two}/" + # ).partial_expand({"one" => "1"}).pattern + # #=> "http://example.com/?one=1{&two}/" + # + # Addressable::Template.new( + # "http://example.com/{?one,two,three}/" + # ).partial_expand({"one" => "1", "three" => 3}).pattern + # #=> "http://example.com/?one=1{&two}&three=3" + def partial_expand(mapping, processor=nil) + result = self.pattern.dup + mapping = normalize_keys(mapping) + result.gsub!( EXPRESSION ) do |capture| + transform_partial_capture(mapping, capture, processor) + end + return Addressable::Template.new(result) + end + + ## + # Expands a URI template into a full URI. + # + # @param [Hash] mapping The mapping that corresponds to the pattern. + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError + # exception will be raised if the value is invalid. The transform + # method should return the transformed variable value as a String. + # If a transform method is used, the value will not be percent + # encoded automatically. Unicode normalization will be performed both + # before and after sending the value to the transform method. + # + # @return [Addressable::URI] The expanded URI template. + # + # @example + # class ExampleProcessor + # def self.validate(name, value) + # return !!(value =~ /^[\w ]+$/) if name == "query" + # return true + # end + # + # def self.transform(name, value) + # return value.gsub(/ /, "+") if name == "query" + # return value + # end + # end + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "an example search query"}, + # ExampleProcessor + # ).to_str + # #=> "http://example.com/search/an+example+search+query/" + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "an example search query"} + # ).to_str + # #=> "http://example.com/search/an%20example%20search%20query/" + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "bogus!"}, + # ExampleProcessor + # ).to_str + # #=> Addressable::Template::InvalidTemplateValueError + def expand(mapping, processor=nil) + result = self.pattern.dup + mapping = normalize_keys(mapping) + result.gsub!( EXPRESSION ) do |capture| + transform_capture(mapping, capture, processor) + end + return Addressable::URI.parse(result) + end + + ## + # Returns an Array of variables used within the template pattern. + # The variables are listed in the Array in the order they appear within + # the pattern. Multiple occurrences of a variable within a pattern are + # not represented in this Array. + # + # @return [Array] The variables present in the template's pattern. + def variables + @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq + end + alias_method :keys, :variables + + ## + # Returns a mapping of variables to their default values specified + # in the template. Variables without defaults are not returned. + # + # @return [Hash] Mapping of template variables to their defaults + def variable_defaults + @variable_defaults ||= + Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten] + end + + private + def ordered_variable_defaults + @ordered_variable_defaults ||= ( + expansions, _ = parse_template_pattern(pattern) + expansions.map do |capture| + _, _, varlist = *capture.match(EXPRESSION) + varlist.split(',').map do |varspec| + varspec[VARSPEC, 1] + end + end.flatten + ) + end + + + ## + # Loops through each capture and expands any values available in mapping + # + # @param [Hash] mapping + # Set of keys to expand + # @param [String] capture + # The expression to expand + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError exception + # will be raised if the value is invalid. The transform method + # should return the transformed variable value as a String. If a + # transform method is used, the value will not be percent encoded + # automatically. Unicode normalization will be performed both before and + # after sending the value to the transform method. + # + # @return [String] The expanded expression + def transform_partial_capture(mapping, capture, processor = nil) + _, operator, varlist = *capture.match(EXPRESSION) + is_first = true + varlist.split(',').inject('') do |acc, varspec| + _, name, _ = *varspec.match(VARSPEC) + value = mapping[name] + if value + operator = '&' if !is_first && operator == '?' + acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor) + else + operator = '&' if !is_first && operator == '?' + acc << "{#{operator}#{varspec}}" + end + is_first = false + acc + end + end + + ## + # Transforms a mapped value so that values can be substituted into the + # template. + # + # @param [Hash] mapping The mapping to replace captures + # @param [String] capture + # The expression to replace + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError exception + # will be raised if the value is invalid. The transform method + # should return the transformed variable value as a String. If a + # transform method is used, the value will not be percent encoded + # automatically. Unicode normalization will be performed both before and + # after sending the value to the transform method. + # + # @return [String] The expanded expression + def transform_capture(mapping, capture, processor=nil) + _, operator, varlist = *capture.match(EXPRESSION) + return_value = varlist.split(',').inject([]) do |acc, varspec| + _, name, modifier = *varspec.match(VARSPEC) + value = mapping[name] + unless value == nil || value == {} + allow_reserved = %w(+ #).include?(operator) + # Common primitives where the .to_s output is well-defined + if Numeric === value || Symbol === value || + value == true || value == false + value = value.to_s + end + length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/ + + unless (Hash === value) || + value.respond_to?(:to_ary) || value.respond_to?(:to_str) + raise TypeError, + "Can't convert #{value.class} into String or Array." + end + + value = normalize_value(value) + + if processor == nil || !processor.respond_to?(:transform) + # Handle percent escaping + if allow_reserved + encode_map = + Addressable::URI::CharacterClasses::RESERVED + + Addressable::URI::CharacterClasses::UNRESERVED + else + encode_map = Addressable::URI::CharacterClasses::UNRESERVED + end + if value.kind_of?(Array) + transformed_value = value.map do |val| + if length + Addressable::URI.encode_component(val[0...length], encode_map) + else + Addressable::URI.encode_component(val, encode_map) + end + end + unless modifier == "*" + transformed_value = transformed_value.join(',') + end + elsif value.kind_of?(Hash) + transformed_value = value.map do |key, val| + if modifier == "*" + "#{ + Addressable::URI.encode_component( key, encode_map) + }=#{ + Addressable::URI.encode_component( val, encode_map) + }" + else + "#{ + Addressable::URI.encode_component( key, encode_map) + },#{ + Addressable::URI.encode_component( val, encode_map) + }" + end + end + unless modifier == "*" + transformed_value = transformed_value.join(',') + end + else + if length + transformed_value = Addressable::URI.encode_component( + value[0...length], encode_map) + else + transformed_value = Addressable::URI.encode_component( + value, encode_map) + end + end + end + + # Process, if we've got a processor + if processor != nil + if processor.respond_to?(:validate) + if !processor.validate(name, value) + display_value = value.kind_of?(Array) ? value.inspect : value + raise InvalidTemplateValueError, + "#{name}=#{display_value} is an invalid template value." + end + end + if processor.respond_to?(:transform) + transformed_value = processor.transform(name, value) + transformed_value = normalize_value(transformed_value) + end + end + acc << [name, transformed_value] + end + acc + end + return "" if return_value.empty? + join_values(operator, return_value) + end + + ## + # Takes a set of values, and joins them together based on the + # operator. + # + # @param [String, Nil] operator One of the operators from the set + # (?,&,+,#,;,/,.), or nil if there wasn't one. + # @param [Array] return_value + # The set of return values (as [variable_name, value] tuples) that will + # be joined together. + # + # @return [String] The transformed mapped value + def join_values(operator, return_value) + leader = LEADERS.fetch(operator, '') + joiner = JOINERS.fetch(operator, ',') + case operator + when '&', '?' + leader + return_value.map{|k,v| + if v.is_a?(Array) && v.first =~ /=/ + v.join(joiner) + elsif v.is_a?(Array) + v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner) + else + "#{k}=#{v}" + end + }.join(joiner) + when ';' + return_value.map{|k,v| + if v.is_a?(Array) && v.first =~ /=/ + ';' + v.join(";") + elsif v.is_a?(Array) + ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";") + else + v && v != '' ? ";#{k}=#{v}" : ";#{k}" + end + }.join + else + leader + return_value.map{|k,v| v}.join(joiner) + end + end + + ## + # Takes a set of values, and joins them together based on the + # operator. + # + # @param [Hash, Array, String] value + # Normalizes keys and values with IDNA#unicode_normalize_kc + # + # @return [Hash, Array, String] The normalized values + def normalize_value(value) + unless value.is_a?(Hash) + value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str + end + + # Handle unicode normalization + if value.kind_of?(Array) + value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) } + elsif value.kind_of?(Hash) + value = value.inject({}) { |acc, (k, v)| + acc[Addressable::IDNA.unicode_normalize_kc(k)] = + Addressable::IDNA.unicode_normalize_kc(v) + acc + } + else + value = Addressable::IDNA.unicode_normalize_kc(value) + end + value + end + + ## + # Generates a hash with string keys + # + # @param [Hash] mapping A mapping hash to normalize + # + # @return [Hash] + # A hash with stringified keys + def normalize_keys(mapping) + return mapping.inject({}) do |accu, pair| + name, value = pair + if Symbol === name + name = name.to_s + elsif name.respond_to?(:to_str) + name = name.to_str + else + raise TypeError, + "Can't convert #{name.class} into String." + end + accu[name] = value + accu + end + end + + ## + # Generates the Regexp that parses a template pattern. + # + # @param [String] pattern The URI template pattern. + # @param [#match] processor The template processor to use. + # + # @return [Regexp] + # A regular expression which may be used to parse a template pattern. + def parse_template_pattern(pattern, processor=nil) + # Escape the pattern. The two gsubs restore the escaped curly braces + # back to their original form. Basically, escape everything that isn't + # within an expansion. + escaped_pattern = Regexp.escape( + pattern + ).gsub(/\\\{(.*?)\\\}/) do |escaped| + escaped.gsub(/\\(.)/, "\\1") + end + + expansions = [] + + # Create a regular expression that captures the values of the + # variables in the URI. + regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion| + + expansions << expansion + _, operator, varlist = *expansion.match(EXPRESSION) + leader = Regexp.escape(LEADERS.fetch(operator, '')) + joiner = Regexp.escape(JOINERS.fetch(operator, ',')) + combined = varlist.split(',').map do |varspec| + _, name, modifier = *varspec.match(VARSPEC) + + result = processor && processor.respond_to?(:match) ? processor.match(name) : nil + if result + "(#{ result })" + else + group = case operator + when '+' + "#{ RESERVED }*?" + when '#' + "#{ RESERVED }*?" + when '/' + "#{ UNRESERVED }*?" + when '.' + "#{ UNRESERVED.gsub('\.', '') }*?" + when ';' + "#{ UNRESERVED }*=?#{ UNRESERVED }*?" + when '?' + "#{ UNRESERVED }*=#{ UNRESERVED }*?" + when '&' + "#{ UNRESERVED }*=#{ UNRESERVED }*?" + else + "#{ UNRESERVED }*?" + end + if modifier == '*' + "(#{group}(?:#{joiner}?#{group})*)?" + else + "(#{group})?" + end + end + end.join("#{joiner}?") + "(?:|#{leader}#{combined})" + end + + # Ensure that the regular expression matches the whole URI. + regexp_string = "^#{regexp_string}$" + return expansions, Regexp.new(regexp_string) + end + + end +end diff --git a/.gems/gems/addressable-2.3.6/lib/addressable/uri.rb b/.gems/gems/addressable-2.3.6/lib/addressable/uri.rb new file mode 100644 index 0000000..e7ddc9f --- /dev/null +++ b/.gems/gems/addressable-2.3.6/lib/addressable/uri.rb @@ -0,0 +1,2351 @@ +# encoding:utf-8 +#-- +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +require "addressable/version" +require "addressable/idna" + +## +# Addressable is a library for processing links and URIs. +module Addressable + ## + # This is an implementation of a URI parser based on + # RFC 3986, + # RFC 3987. + class URI + ## + # Raised if something other than a uri is supplied. + class InvalidURIError < StandardError + end + + ## + # Container for the character classes specified in + # RFC 3986. + module CharacterClasses + ALPHA = "a-zA-Z" + DIGIT = "0-9" + GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@" + SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=" + RESERVED = GEN_DELIMS + SUB_DELIMS + UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~" + PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@" + SCHEME = ALPHA + DIGIT + "\\-\\+\\." + AUTHORITY = PCHAR + PATH = PCHAR + "\\/" + QUERY = PCHAR + "\\/\\?" + FRAGMENT = PCHAR + "\\/\\?" + end + + SLASH = '/' + EMPTY_STR = '' + + URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ + + PORT_MAPPING = { + "http" => 80, + "https" => 443, + "ftp" => 21, + "tftp" => 69, + "sftp" => 22, + "ssh" => 22, + "svn+ssh" => 22, + "telnet" => 23, + "nntp" => 119, + "gopher" => 70, + "wais" => 210, + "ldap" => 389, + "prospero" => 1525 + } + + ## + # Returns a URI object based on the parsed string. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI string to parse. + # No parsing is performed if the object is already an + # Addressable::URI. + # + # @return [Addressable::URI] The parsed URI. + def self.parse(uri) + # If we were given nil, return nil. + return nil unless uri + # If a URI object is passed, just return itself. + return uri.dup if uri.kind_of?(self) + + # If a URI object of the Ruby standard library variety is passed, + # convert it to a string, then parse the string. + # We do the check this way because we don't want to accidentally + # cause a missing constant exception to be thrown. + if uri.class.name =~ /^URI\b/ + uri = uri.to_s + end + + # Otherwise, convert to a String + begin + uri = uri.to_str + rescue TypeError, NoMethodError + raise TypeError, "Can't convert #{uri.class} into String." + end if not uri.is_a? String + + # This Regexp supplied as an example in RFC 3986, and it works great. + scan = uri.scan(URIREGEX) + fragments = scan[0] + scheme = fragments[1] + authority = fragments[3] + path = fragments[4] + query = fragments[6] + fragment = fragments[8] + user = nil + password = nil + host = nil + port = nil + if authority != nil + # The Regexp above doesn't split apart the authority. + userinfo = authority[/^([^\[\]]*)@/, 1] + if userinfo != nil + user = userinfo.strip[/^([^:]*):?/, 1] + password = userinfo.strip[/:(.*)$/, 1] + end + host = authority.gsub( + /^([^\[\]]*)@/, EMPTY_STR + ).gsub( + /:([^:@\[\]]*?)$/, EMPTY_STR + ) + port = authority[/:([^:@\[\]]*?)$/, 1] + end + if port == EMPTY_STR + port = nil + end + + return new( + :scheme => scheme, + :user => user, + :password => password, + :host => host, + :port => port, + :path => path, + :query => query, + :fragment => fragment + ) + end + + ## + # Converts an input to a URI. The input does not have to be a valid + # URI — the method will use heuristics to guess what URI was intended. + # This is not standards-compliant, merely user-friendly. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI string to parse. + # No parsing is performed if the object is already an + # Addressable::URI. + # @param [Hash] hints + # A Hash of hints to the heuristic parser. + # Defaults to {:scheme => "http"}. + # + # @return [Addressable::URI] The parsed URI. + def self.heuristic_parse(uri, hints={}) + # If we were given nil, return nil. + return nil unless uri + # If a URI object is passed, just return itself. + return uri.dup if uri.kind_of?(self) + + # If a URI object of the Ruby standard library variety is passed, + # convert it to a string, then parse the string. + # We do the check this way because we don't want to accidentally + # cause a missing constant exception to be thrown. + if uri.class.name =~ /^URI\b/ + uri = uri.to_s + end + + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + # Otherwise, convert to a String + uri = uri.to_str.dup + hints = { + :scheme => "http" + }.merge(hints) + case uri + when /^http:\/+/ + uri.gsub!(/^http:\/+/, "http://") + when /^https:\/+/ + uri.gsub!(/^https:\/+/, "https://") + when /^feed:\/+http:\/+/ + uri.gsub!(/^feed:\/+http:\/+/, "feed:http://") + when /^feed:\/+/ + uri.gsub!(/^feed:\/+/, "feed://") + when /^file:\/+/ + uri.gsub!(/^file:\/+/, "file:///") + when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ + uri.gsub!(/^/, hints[:scheme] + "://") + end + parsed = self.parse(uri) + if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/ + parsed = self.parse(hints[:scheme] + "://" + uri) + end + if parsed.path.include?(".") + new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1] + if new_host + parsed.defer_validation do + new_path = parsed.path.gsub( + Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR) + parsed.host = new_host + parsed.path = new_path + parsed.scheme = hints[:scheme] unless parsed.scheme + end + end + end + return parsed + end + + ## + # Converts a path to a file scheme URI. If the path supplied is + # relative, it will be returned as a relative URI. If the path supplied + # is actually a non-file URI, it will parse the URI as if it had been + # parsed with Addressable::URI.parse. Handles all of the + # various Microsoft-specific formats for specifying paths. + # + # @param [String, Addressable::URI, #to_str] path + # Typically a String path to a file or directory, but + # will return a sensible return value if an absolute URI is supplied + # instead. + # + # @return [Addressable::URI] + # The parsed file scheme URI or the original URI if some other URI + # scheme was provided. + # + # @example + # base = Addressable::URI.convert_path("/absolute/path/") + # uri = Addressable::URI.convert_path("relative/path") + # (base + uri).to_s + # #=> "file:///absolute/path/relative/path" + # + # Addressable::URI.convert_path( + # "c:\\windows\\My Documents 100%20\\foo.txt" + # ).to_s + # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt" + # + # Addressable::URI.convert_path("http://example.com/").to_s + # #=> "http://example.com/" + def self.convert_path(path) + # If we were given nil, return nil. + return nil unless path + # If a URI object is passed, just return itself. + return path if path.kind_of?(self) + if !path.respond_to?(:to_str) + raise TypeError, "Can't convert #{path.class} into String." + end + # Otherwise, convert to a String + path = path.to_str.strip + + path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/ + path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/ + uri = self.parse(path) + + if uri.scheme == nil + # Adjust windows-style uris + uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do + "/#{$1.downcase}:/" + end + uri.path.gsub!(/\\/, SLASH) + if File.exists?(uri.path) && + File.stat(uri.path).directory? + uri.path.gsub!(/\/$/, EMPTY_STR) + uri.path = uri.path + '/' + end + + # If the path is absolute, set the scheme and host. + if uri.path =~ /^\// + uri.scheme = "file" + uri.host = EMPTY_STR + end + uri.normalize! + end + + return uri + end + + ## + # Joins several URIs together. + # + # @param [String, Addressable::URI, #to_str] *uris + # The URIs to join. + # + # @return [Addressable::URI] The joined URI. + # + # @example + # base = "http://example.com/" + # uri = Addressable::URI.parse("relative/path") + # Addressable::URI.join(base, uri) + # #=> # + def self.join(*uris) + uri_objects = uris.collect do |uri| + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + uri.kind_of?(self) ? uri : self.parse(uri.to_str) + end + result = uri_objects.shift.dup + for uri in uri_objects + result.join!(uri) + end + return result + end + + ## + # Percent encodes a URI component. + # + # @param [String, #to_str] component The URI component to encode. + # + # @param [String, Regexp] character_class + # The characters which are not percent encoded. If a String + # is passed, the String must be formatted as a regular + # expression character class. (Do not include the surrounding square + # brackets.) For example, "b-zB-Z0-9" would cause + # everything but the letters 'b' through 'z' and the numbers '0' through + # '9' to be percent encoded. If a Regexp is passed, the + # value /[^b-zB-Z0-9]/ would have the same effect. A set of + # useful String values may be found in the + # Addressable::URI::CharacterClasses module. The default + # value is the reserved plus unreserved character classes specified in + # RFC 3986. + # + # @param [Regexp] upcase_encoded + # A string of characters that may already be percent encoded, and whose + # encodings should be upcased. This allows normalization of percent + # encodings for characters not included in the + # character_class. + # + # @return [String] The encoded component. + # + # @example + # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9") + # => "simple%2Fex%61mple" + # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/) + # => "simple%2Fex%61mple" + # Addressable::URI.encode_component( + # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED + # ) + # => "simple%2Fexample" + def self.encode_component(component, character_class= + CharacterClasses::RESERVED + CharacterClasses::UNRESERVED, + upcase_encoded='') + return nil if component.nil? + + begin + if component.kind_of?(Symbol) || + component.kind_of?(Numeric) || + component.kind_of?(TrueClass) || + component.kind_of?(FalseClass) + component = component.to_s + else + component = component.to_str + end + rescue TypeError, NoMethodError + raise TypeError, "Can't convert #{component.class} into String." + end if !component.is_a? String + + if ![String, Regexp].include?(character_class.class) + raise TypeError, + "Expected String or Regexp, got #{character_class.inspect}" + end + if character_class.kind_of?(String) + character_class = /[^#{character_class}]/ + end + if component.respond_to?(:force_encoding) + # We can't perform regexps on invalid UTF sequences, but + # here we need to, so switch to ASCII. + component = component.dup + component.force_encoding(Encoding::ASCII_8BIT) + end + # Avoiding gsub! because there are edge cases with frozen strings + component = component.gsub(character_class) do |sequence| + (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join + end + if upcase_encoded.length > 0 + component = component.gsub(/%(#{upcase_encoded.chars.map do |char| + char.unpack('C*').map { |c| '%02x' % c }.join + end.join('|')})/i) { |s| s.upcase } + end + return component + end + + class << self + alias_method :encode_component, :encode_component + end + + ## + # Unencodes any percent encoded characters within a URI component. + # This method may be used for unencoding either components or full URIs, + # however, it is recommended to use the unencode_component + # alias when unencoding components. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI or component to unencode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @param [String] leave_encoded + # A string of characters to leave encoded. If a percent encoded character + # in this list is encountered then it will remain percent encoded. + # + # @return [String, Addressable::URI] + # The unencoded component or URI. + # The return type is determined by the return_type + # parameter. + def self.unencode(uri, return_type=String, leave_encoded='') + return nil if uri.nil? + + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri = uri.dup + # Seriously, only use UTF-8. I'm really not kidding! + uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding) + leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding) + result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence| + c = sequence[1..3].to_i(16).chr + c.force_encoding("utf-8") if c.respond_to?(:force_encoding) + leave_encoded.include?(c) ? sequence : c + end + result.force_encoding("utf-8") if result.respond_to?(:force_encoding) + if return_type == String + return result + elsif return_type == ::Addressable::URI + return ::Addressable::URI.parse(result) + end + end + + class << self + alias_method :unescape, :unencode + alias_method :unencode_component, :unencode + alias_method :unescape_component, :unencode + end + + + ## + # Normalizes the encoding of a URI component. + # + # @param [String, #to_str] component The URI component to encode. + # + # @param [String, Regexp] character_class + # The characters which are not percent encoded. If a String + # is passed, the String must be formatted as a regular + # expression character class. (Do not include the surrounding square + # brackets.) For example, "b-zB-Z0-9" would cause + # everything but the letters 'b' through 'z' and the numbers '0' + # through '9' to be percent encoded. If a Regexp is passed, + # the value /[^b-zB-Z0-9]/ would have the same effect. A + # set of useful String values may be found in the + # Addressable::URI::CharacterClasses module. The default + # value is the reserved plus unreserved character classes specified in + # RFC 3986. + # + # @param [String] leave_encoded + # When character_class is a String then + # leave_encoded is a string of characters that should remain + # percent encoded while normalizing the component; if they appear percent + # encoded in the original component, then they will be upcased ("%2f" + # normalized to "%2F") but otherwise left alone. + # + # @return [String] The normalized component. + # + # @example + # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z") + # => "simple%2Fex%61mple" + # Addressable::URI.normalize_component( + # "simpl%65/%65xampl%65", /[^b-zB-Z]/ + # ) + # => "simple%2Fex%61mple" + # Addressable::URI.normalize_component( + # "simpl%65/%65xampl%65", + # Addressable::URI::CharacterClasses::UNRESERVED + # ) + # => "simple%2Fexample" + # Addressable::URI.normalize_component( + # "one%20two%2fthree%26four", + # "0-9a-zA-Z &/", + # "/" + # ) + # => "one two%2Fthree&four" + def self.normalize_component(component, character_class= + CharacterClasses::RESERVED + CharacterClasses::UNRESERVED, + leave_encoded='') + return nil if component.nil? + + begin + component = component.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{component.class} into String." + end if !component.is_a? String + + if ![String, Regexp].include?(character_class.class) + raise TypeError, + "Expected String or Regexp, got #{character_class.inspect}" + end + if character_class.kind_of?(String) + leave_re = if leave_encoded.length > 0 + character_class = "#{character_class}%" unless character_class.include?('%') + + "|%(?!#{leave_encoded.chars.map do |char| + seq = char.unpack('C*').map { |c| '%02x' % c }.join + [seq.upcase, seq.downcase] + end.flatten.join('|')})" + end + + character_class = /[^#{character_class}]#{leave_re}/ + end + if component.respond_to?(:force_encoding) + # We can't perform regexps on invalid UTF sequences, but + # here we need to, so switch to ASCII. + component = component.dup + component.force_encoding(Encoding::ASCII_8BIT) + end + unencoded = self.unencode_component(component, String, leave_encoded) + begin + encoded = self.encode_component( + Addressable::IDNA.unicode_normalize_kc(unencoded), + character_class, + leave_encoded + ) + rescue ArgumentError + encoded = self.encode_component(unencoded) + end + if encoded.respond_to?(:force_encoding) + encoded.force_encoding(Encoding::UTF_8) + end + return encoded + end + + ## + # Percent encodes any special characters in the URI. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI to encode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @return [String, Addressable::URI] + # The encoded URI. + # The return type is determined by the return_type + # parameter. + def self.encode(uri, return_type=String) + return nil if uri.nil? + + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri_object = uri.kind_of?(self) ? uri : self.parse(uri) + encoded_uri = Addressable::URI.new( + :scheme => self.encode_component(uri_object.scheme, + Addressable::URI::CharacterClasses::SCHEME), + :authority => self.encode_component(uri_object.authority, + Addressable::URI::CharacterClasses::AUTHORITY), + :path => self.encode_component(uri_object.path, + Addressable::URI::CharacterClasses::PATH), + :query => self.encode_component(uri_object.query, + Addressable::URI::CharacterClasses::QUERY), + :fragment => self.encode_component(uri_object.fragment, + Addressable::URI::CharacterClasses::FRAGMENT) + ) + if return_type == String + return encoded_uri.to_s + elsif return_type == ::Addressable::URI + return encoded_uri + end + end + + class << self + alias_method :escape, :encode + end + + ## + # Normalizes the encoding of a URI. Characters within a hostname are + # not percent encoded to allow for internationalized domain names. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI to encode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @return [String, Addressable::URI] + # The encoded URI. + # The return type is determined by the return_type + # parameter. + def self.normalized_encode(uri, return_type=String) + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri_object = uri.kind_of?(self) ? uri : self.parse(uri) + components = { + :scheme => self.unencode_component(uri_object.scheme), + :user => self.unencode_component(uri_object.user), + :password => self.unencode_component(uri_object.password), + :host => self.unencode_component(uri_object.host), + :port => (uri_object.port.nil? ? nil : uri_object.port.to_s), + :path => self.unencode_component(uri_object.path), + :query => self.unencode_component(uri_object.query), + :fragment => self.unencode_component(uri_object.fragment) + } + components.each do |key, value| + if value != nil + begin + components[key] = + Addressable::IDNA.unicode_normalize_kc(value.to_str) + rescue ArgumentError + # Likely a malformed UTF-8 character, skip unicode normalization + components[key] = value.to_str + end + end + end + encoded_uri = Addressable::URI.new( + :scheme => self.encode_component(components[:scheme], + Addressable::URI::CharacterClasses::SCHEME), + :user => self.encode_component(components[:user], + Addressable::URI::CharacterClasses::UNRESERVED), + :password => self.encode_component(components[:password], + Addressable::URI::CharacterClasses::UNRESERVED), + :host => components[:host], + :port => components[:port], + :path => self.encode_component(components[:path], + Addressable::URI::CharacterClasses::PATH), + :query => self.encode_component(components[:query], + Addressable::URI::CharacterClasses::QUERY), + :fragment => self.encode_component(components[:fragment], + Addressable::URI::CharacterClasses::FRAGMENT) + ) + if return_type == String + return encoded_uri.to_s + elsif return_type == ::Addressable::URI + return encoded_uri + end + end + + ## + # Encodes a set of key/value pairs according to the rules for the + # application/x-www-form-urlencoded MIME type. + # + # @param [#to_hash, #to_ary] form_values + # The form values to encode. + # + # @param [TrueClass, FalseClass] sort + # Sort the key/value pairs prior to encoding. + # Defaults to false. + # + # @return [String] + # The encoded value. + def self.form_encode(form_values, sort=false) + if form_values.respond_to?(:to_hash) + form_values = form_values.to_hash.to_a + elsif form_values.respond_to?(:to_ary) + form_values = form_values.to_ary + else + raise TypeError, "Can't convert #{form_values.class} into Array." + end + + form_values = form_values.inject([]) do |accu, (key, value)| + if value.kind_of?(Array) + value.each do |v| + accu << [key.to_s, v.to_s] + end + else + accu << [key.to_s, value.to_s] + end + accu + end + + if sort + # Useful for OAuth and optimizing caching systems + form_values = form_values.sort + end + escaped_form_values = form_values.map do |(key, value)| + # Line breaks are CRLF pairs + [ + self.encode_component( + key.gsub(/(\r\n|\n|\r)/, "\r\n"), + CharacterClasses::UNRESERVED + ).gsub("%20", "+"), + self.encode_component( + value.gsub(/(\r\n|\n|\r)/, "\r\n"), + CharacterClasses::UNRESERVED + ).gsub("%20", "+") + ] + end + return (escaped_form_values.map do |(key, value)| + "#{key}=#{value}" + end).join("&") + end + + ## + # Decodes a String according to the rules for the + # application/x-www-form-urlencoded MIME type. + # + # @param [String, #to_str] encoded_value + # The form values to decode. + # + # @return [Array] + # The decoded values. + # This is not a Hash because of the possibility for + # duplicate keys. + def self.form_unencode(encoded_value) + if !encoded_value.respond_to?(:to_str) + raise TypeError, "Can't convert #{encoded_value.class} into String." + end + encoded_value = encoded_value.to_str + split_values = encoded_value.split("&").map do |pair| + pair.split("=", 2) + end + return split_values.map do |(key, value)| + [ + key ? self.unencode_component( + key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil, + value ? (self.unencode_component( + value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil + ] + end + end + + ## + # Creates a new uri object from component parts. + # + # @option [String, #to_str] scheme The scheme component. + # @option [String, #to_str] user The user component. + # @option [String, #to_str] password The password component. + # @option [String, #to_str] userinfo + # The userinfo component. If this is supplied, the user and password + # components must be omitted. + # @option [String, #to_str] host The host component. + # @option [String, #to_str] port The port component. + # @option [String, #to_str] authority + # The authority component. If this is supplied, the user, password, + # userinfo, host, and port components must be omitted. + # @option [String, #to_str] path The path component. + # @option [String, #to_str] query The query component. + # @option [String, #to_str] fragment The fragment component. + # + # @return [Addressable::URI] The constructed URI object. + def initialize(options={}) + if options.has_key?(:authority) + if (options.keys & [:userinfo, :user, :password, :host, :port]).any? + raise ArgumentError, + "Cannot specify both an authority and any of the components " + + "within the authority." + end + end + if options.has_key?(:userinfo) + if (options.keys & [:user, :password]).any? + raise ArgumentError, + "Cannot specify both a userinfo and either the user or password." + end + end + + self.defer_validation do + # Bunch of crazy logic required because of the composite components + # like userinfo and authority. + self.scheme = options[:scheme] if options[:scheme] + self.user = options[:user] if options[:user] + self.password = options[:password] if options[:password] + self.userinfo = options[:userinfo] if options[:userinfo] + self.host = options[:host] if options[:host] + self.port = options[:port] if options[:port] + self.authority = options[:authority] if options[:authority] + self.path = options[:path] if options[:path] + self.query = options[:query] if options[:query] + self.query_values = options[:query_values] if options[:query_values] + self.fragment = options[:fragment] if options[:fragment] + end + end + + ## + # Freeze URI, initializing instance variables. + # + # @return [Addressable::URI] The frozen URI object. + def freeze + self.normalized_scheme + self.normalized_user + self.normalized_password + self.normalized_userinfo + self.normalized_host + self.normalized_port + self.normalized_authority + self.normalized_site + self.normalized_path + self.normalized_query + self.normalized_fragment + self.hash + super + end + + ## + # The scheme component for this URI. + # + # @return [String] The scheme component. + def scheme + return instance_variable_defined?(:@scheme) ? @scheme : nil + end + + ## + # The scheme component for this URI, normalized. + # + # @return [String] The scheme component, normalized. + def normalized_scheme + self.scheme && @normalized_scheme ||= (begin + if self.scheme =~ /^\s*ssh\+svn\s*$/i + "svn+ssh" + else + Addressable::URI.normalize_component( + self.scheme.strip.downcase, + Addressable::URI::CharacterClasses::SCHEME + ) + end + end) + end + + ## + # Sets the scheme component for this URI. + # + # @param [String, #to_str] new_scheme The new scheme component. + def scheme=(new_scheme) + if new_scheme && !new_scheme.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_scheme.class} into String." + elsif new_scheme + new_scheme = new_scheme.to_str + end + if new_scheme && new_scheme !~ /[a-z][a-z0-9\.\+\-]*/i + raise InvalidURIError, "Invalid scheme format." + end + @scheme = new_scheme + @scheme = nil if @scheme.to_s.strip.empty? + + # Reset dependant values + @normalized_scheme = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The user component for this URI. + # + # @return [String] The user component. + def user + return instance_variable_defined?(:@user) ? @user : nil + end + + ## + # The user component for this URI, normalized. + # + # @return [String] The user component, normalized. + def normalized_user + self.user && @normalized_user ||= (begin + if normalized_scheme =~ /https?/ && self.user.strip.empty? && + (!self.password || self.password.strip.empty?) + nil + else + Addressable::URI.normalize_component( + self.user.strip, + Addressable::URI::CharacterClasses::UNRESERVED + ) + end + end) + end + + ## + # Sets the user component for this URI. + # + # @param [String, #to_str] new_user The new user component. + def user=(new_user) + if new_user && !new_user.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_user.class} into String." + end + @user = new_user ? new_user.to_str : nil + + # You can't have a nil user with a non-nil password + if password != nil + @user = EMPTY_STR if @user.nil? + end + + # Reset dependant values + @userinfo = nil + @normalized_userinfo = nil + @authority = nil + @normalized_user = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The password component for this URI. + # + # @return [String] The password component. + def password + return instance_variable_defined?(:@password) ? @password : nil + end + + ## + # The password component for this URI, normalized. + # + # @return [String] The password component, normalized. + def normalized_password + self.password && @normalized_password ||= (begin + if self.normalized_scheme =~ /https?/ && self.password.strip.empty? && + (!self.user || self.user.strip.empty?) + nil + else + Addressable::URI.normalize_component( + self.password.strip, + Addressable::URI::CharacterClasses::UNRESERVED + ) + end + end) + end + + ## + # Sets the password component for this URI. + # + # @param [String, #to_str] new_password The new password component. + def password=(new_password) + if new_password && !new_password.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_password.class} into String." + end + @password = new_password ? new_password.to_str : nil + + # You can't have a nil user with a non-nil password + @password ||= nil + @user ||= nil + if @password != nil + @user = EMPTY_STR if @user.nil? + end + + # Reset dependant values + @userinfo = nil + @normalized_userinfo = nil + @authority = nil + @normalized_password = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The userinfo component for this URI. + # Combines the user and password components. + # + # @return [String] The userinfo component. + def userinfo + current_user = self.user + current_password = self.password + (current_user || current_password) && @userinfo ||= (begin + if current_user && current_password + "#{current_user}:#{current_password}" + elsif current_user && !current_password + "#{current_user}" + end + end) + end + + ## + # The userinfo component for this URI, normalized. + # + # @return [String] The userinfo component, normalized. + def normalized_userinfo + self.userinfo && @normalized_userinfo ||= (begin + current_user = self.normalized_user + current_password = self.normalized_password + if !current_user && !current_password + nil + elsif current_user && current_password + "#{current_user}:#{current_password}" + elsif current_user && !current_password + "#{current_user}" + end + end) + end + + ## + # Sets the userinfo component for this URI. + # + # @param [String, #to_str] new_userinfo The new userinfo component. + def userinfo=(new_userinfo) + if new_userinfo && !new_userinfo.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_userinfo.class} into String." + end + new_user, new_password = if new_userinfo + [ + new_userinfo.to_str.strip[/^(.*):/, 1], + new_userinfo.to_str.strip[/:(.*)$/, 1] + ] + else + [nil, nil] + end + + # Password assigned first to ensure validity in case of nil + self.password = new_password + self.user = new_user + + # Reset dependant values + @authority = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The host component for this URI. + # + # @return [String] The host component. + def host + return instance_variable_defined?(:@host) ? @host : nil + end + + ## + # The host component for this URI, normalized. + # + # @return [String] The host component, normalized. + def normalized_host + self.host && @normalized_host ||= (begin + if !self.host.strip.empty? + result = ::Addressable::IDNA.to_ascii( + URI.unencode_component(self.host.strip.downcase) + ) + if result =~ /[^\.]\.$/ + # Single trailing dots are unnecessary. + result = result[0...-1] + end + result + else + EMPTY_STR + end + end) + end + + ## + # Sets the host component for this URI. + # + # @param [String, #to_str] new_host The new host component. + def host=(new_host) + if new_host && !new_host.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_host.class} into String." + end + @host = new_host ? new_host.to_str : nil + + unreserved = CharacterClasses::UNRESERVED + sub_delims = CharacterClasses::SUB_DELIMS + if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ || + (@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~ + Regexp.new("^[#{unreserved}#{sub_delims}:]*$"))) + raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'" + end + + # Reset dependant values + @authority = nil + @normalized_host = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # This method is same as URI::Generic#host except + # brackets for IPv6 (and 'IPvFuture') addresses are removed. + # + # @see Addressable::URI#host + # + # @return [String] The hostname for this URI. + def hostname + v = self.host + /\A\[(.*)\]\z/ =~ v ? $1 : v + end + + ## + # This method is same as URI::Generic#host= except + # the argument can be a bare IPv6 address (or 'IPvFuture'). + # + # @see Addressable::URI#host= + # + # @param [String, #to_str] new_hostname The new hostname for this URI. + def hostname=(new_hostname) + if new_hostname && !new_hostname.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_hostname.class} into String." + end + v = new_hostname ? new_hostname.to_str : nil + v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + self.host = v + end + + ## + # The authority component for this URI. + # Combines the user, password, host, and port components. + # + # @return [String] The authority component. + def authority + self.host && @authority ||= (begin + authority = "" + if self.userinfo != nil + authority << "#{self.userinfo}@" + end + authority << self.host + if self.port != nil + authority << ":#{self.port}" + end + authority + end) + end + + ## + # The authority component for this URI, normalized. + # + # @return [String] The authority component, normalized. + def normalized_authority + self.authority && @normalized_authority ||= (begin + authority = "" + if self.normalized_userinfo != nil + authority << "#{self.normalized_userinfo}@" + end + authority << self.normalized_host + if self.normalized_port != nil + authority << ":#{self.normalized_port}" + end + authority + end) + end + + ## + # Sets the authority component for this URI. + # + # @param [String, #to_str] new_authority The new authority component. + def authority=(new_authority) + if new_authority + if !new_authority.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_authority.class} into String." + end + new_authority = new_authority.to_str + new_userinfo = new_authority[/^([^\[\]]*)@/, 1] + if new_userinfo + new_user = new_userinfo.strip[/^([^:]*):?/, 1] + new_password = new_userinfo.strip[/:(.*)$/, 1] + end + new_host = new_authority.gsub( + /^([^\[\]]*)@/, EMPTY_STR + ).gsub( + /:([^:@\[\]]*?)$/, EMPTY_STR + ) + new_port = + new_authority[/:([^:@\[\]]*?)$/, 1] + end + + # Password assigned first to ensure validity in case of nil + self.password = defined?(new_password) ? new_password : nil + self.user = defined?(new_user) ? new_user : nil + self.host = defined?(new_host) ? new_host : nil + self.port = defined?(new_port) ? new_port : nil + + # Reset dependant values + @userinfo = nil + @normalized_userinfo = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The origin for this URI, serialized to ASCII, as per + # RFC 6454, section 6.2. + # + # @return [String] The serialized origin. + def origin + return (if self.scheme && self.authority + if self.normalized_port + ( + "#{self.normalized_scheme}://#{self.normalized_host}" + + ":#{self.normalized_port}" + ) + else + "#{self.normalized_scheme}://#{self.normalized_host}" + end + else + "null" + end) + end + + # Returns an array of known ip-based schemes. These schemes typically + # use a similar URI form: + # //:@:/ + def self.ip_based_schemes + return self.port_mapping.keys + end + + # Returns a hash of common IP-based schemes and their default port + # numbers. Adding new schemes to this hash, as necessary, will allow + # for better URI normalization. + def self.port_mapping + PORT_MAPPING + end + + ## + # The port component for this URI. + # This is the port number actually given in the URI. This does not + # infer port numbers from default values. + # + # @return [Integer] The port component. + def port + return instance_variable_defined?(:@port) ? @port : nil + end + + ## + # The port component for this URI, normalized. + # + # @return [Integer] The port component, normalized. + def normalized_port + if URI.port_mapping[self.normalized_scheme] == self.port + nil + else + self.port + end + end + + ## + # Sets the port component for this URI. + # + # @param [String, Integer, #to_s] new_port The new port component. + def port=(new_port) + if new_port != nil && new_port.respond_to?(:to_str) + new_port = Addressable::URI.unencode_component(new_port.to_str) + end + if new_port != nil && !(new_port.to_s =~ /^\d+$/) + raise InvalidURIError, + "Invalid port number: #{new_port.inspect}" + end + + @port = new_port.to_s.to_i + @port = nil if @port == 0 + + # Reset dependant values + @authority = nil + @normalized_port = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The inferred port component for this URI. + # This method will normalize to the default port for the URI's scheme if + # the port isn't explicitly specified in the URI. + # + # @return [Integer] The inferred port component. + def inferred_port + if self.port.to_i == 0 + self.default_port + else + self.port.to_i + end + end + + ## + # The default port for this URI's scheme. + # This method will always returns the default port for the URI's scheme + # regardless of the presence of an explicit port in the URI. + # + # @return [Integer] The default port. + def default_port + URI.port_mapping[self.scheme.strip.downcase] if self.scheme + end + + ## + # The combination of components that represent a site. + # Combines the scheme, user, password, host, and port components. + # Primarily useful for HTTP and HTTPS. + # + # For example, "http://example.com/path?query" would have a + # site value of "http://example.com". + # + # @return [String] The components that identify a site. + def site + (self.scheme || self.authority) && @site ||= (begin + site_string = "" + site_string << "#{self.scheme}:" if self.scheme != nil + site_string << "//#{self.authority}" if self.authority != nil + site_string + end) + end + + ## + # The normalized combination of components that represent a site. + # Combines the scheme, user, password, host, and port components. + # Primarily useful for HTTP and HTTPS. + # + # For example, "http://example.com/path?query" would have a + # site value of "http://example.com". + # + # @return [String] The normalized components that identify a site. + def normalized_site + self.site && @normalized_site ||= (begin + site_string = "" + if self.normalized_scheme != nil + site_string << "#{self.normalized_scheme}:" + end + if self.normalized_authority != nil + site_string << "//#{self.normalized_authority}" + end + site_string + end) + end + + ## + # Sets the site value for this URI. + # + # @param [String, #to_str] new_site The new site value. + def site=(new_site) + if new_site + if !new_site.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_site.class} into String." + end + new_site = new_site.to_str + # These two regular expressions derived from the primary parsing + # expression + self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1] + self.authority = new_site[ + /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1 + ] + else + self.scheme = nil + self.authority = nil + end + end + + ## + # The path component for this URI. + # + # @return [String] The path component. + def path + return instance_variable_defined?(:@path) ? @path : EMPTY_STR + end + + NORMPATH = /^(?!\/)[^\/:]*:.*$/ + ## + # The path component for this URI, normalized. + # + # @return [String] The path component, normalized. + def normalized_path + @normalized_path ||= (begin + path = self.path.to_s + if self.scheme == nil && path =~ NORMPATH + # Relative paths with colons in the first segment are ambiguous. + path = path.sub(":", "%2F") + end + # String#split(delimeter, -1) uses the more strict splitting behavior + # found by default in Python. + result = (path.strip.split(SLASH, -1).map do |segment| + Addressable::URI.normalize_component( + segment, + Addressable::URI::CharacterClasses::PCHAR + ) + end).join(SLASH) + + result = URI.normalize_path(result) + if result.empty? && + ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme) + result = SLASH + end + result + end) + end + + ## + # Sets the path component for this URI. + # + # @param [String, #to_str] new_path The new path component. + def path=(new_path) + if new_path && !new_path.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_path.class} into String." + end + @path = (new_path || EMPTY_STR).to_str + if !@path.empty? && @path[0..0] != SLASH && host != nil + @path = "/#{@path}" + end + + # Reset dependant values + @normalized_path = nil + @uri_string = nil + @hash = nil + end + + ## + # The basename, if any, of the file in the path component. + # + # @return [String] The path's basename. + def basename + # Path cannot be nil + return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR) + end + + ## + # The extname, if any, of the file in the path component. + # Empty string if there is no extension. + # + # @return [String] The path's extname. + def extname + return nil unless self.path + return File.extname(self.basename) + end + + ## + # The query component for this URI. + # + # @return [String] The query component. + def query + return instance_variable_defined?(:@query) ? @query : nil + end + + ## + # The query component for this URI, normalized. + # + # @return [String] The query component, normalized. + def normalized_query(*flags) + modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup + # Make sure possible key-value pair delimiters are escaped. + modified_query_class.sub!("\\&", "").sub!("\\;", "") + pairs = (self.query || "").split("&", -1) + pairs.sort! if flags.include?(:sorted) + component = (pairs.map do |pair| + Addressable::URI.normalize_component(pair, modified_query_class, "+") + end).join("&") + component == "" ? nil : component + end + + ## + # Sets the query component for this URI. + # + # @param [String, #to_str] new_query The new query component. + def query=(new_query) + if new_query && !new_query.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_query.class} into String." + end + @query = new_query ? new_query.to_str : nil + + # Reset dependant values + @normalized_query = nil + @uri_string = nil + @hash = nil + end + + ## + # Converts the query component to a Hash value. + # + # @param [Class] return_type The return type desired. Value must be either + # `Hash` or `Array`. + # + # @return [Hash, Array] The query string parsed as a Hash or Array object. + # + # @example + # Addressable::URI.parse("?one=1&two=2&three=3").query_values + # #=> {"one" => "1", "two" => "2", "three" => "3"} + # Addressable::URI.parse("?one=two&one=three").query_values(Array) + # #=> [["one", "two"], ["one", "three"]] + # Addressable::URI.parse("?one=two&one=three").query_values(Hash) + # #=> {"one" => "three"} + def query_values(return_type=Hash) + empty_accumulator = Array == return_type ? [] : {} + if return_type != Hash && return_type != Array + raise ArgumentError, "Invalid return type. Must be Hash or Array." + end + return nil if self.query == nil + split_query = (self.query.split("&").map do |pair| + pair.split("=", 2) if pair && !pair.empty? + end).compact + return split_query.inject(empty_accumulator.dup) do |accu, pair| + # I'd rather use key/value identifiers instead of array lookups, + # but in this case I really want to maintain the exact pair structure, + # so it's best to make all changes in-place. + pair[0] = URI.unencode_component(pair[0]) + if pair[1].respond_to?(:to_str) + # I loathe the fact that I have to do this. Stupid HTML 4.01. + # Treating '+' as a space was just an unbelievably bad idea. + # There was nothing wrong with '%20'! + # If it ain't broke, don't fix it! + pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " ")) + end + if return_type == Hash + accu[pair[0]] = pair[1] + else + accu << pair + end + accu + end + end + + ## + # Sets the query component for this URI from a Hash object. + # An empty Hash or Array will result in an empty query string. + # + # @param [Hash, #to_hash, Array] new_query_values The new query values. + # + # @example + # uri.query_values = {:a => "a", :b => ["c", "d", "e"]} + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']] + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]] + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['flag'], ['key', 'value']] + # uri.query + # # => "flag&key=value" + def query_values=(new_query_values) + if new_query_values == nil + self.query = nil + return nil + end + + if !new_query_values.is_a?(Array) + if !new_query_values.respond_to?(:to_hash) + raise TypeError, + "Can't convert #{new_query_values.class} into Hash." + end + new_query_values = new_query_values.to_hash + new_query_values = new_query_values.map do |key, value| + key = key.to_s if key.kind_of?(Symbol) + [key, value] + end + # Useful default for OAuth and caching. + # Only to be used for non-Array inputs. Arrays should preserve order. + new_query_values.sort! + end + + # new_query_values have form [['key1', 'value1'], ['key2', 'value2']] + buffer = "" + new_query_values.each do |key, value| + encoded_key = URI.encode_component( + key, CharacterClasses::UNRESERVED + ) + if value == nil + buffer << "#{encoded_key}&" + elsif value.kind_of?(Array) + value.each do |sub_value| + encoded_value = URI.encode_component( + sub_value, CharacterClasses::UNRESERVED + ) + buffer << "#{encoded_key}=#{encoded_value}&" + end + else + encoded_value = URI.encode_component( + value, CharacterClasses::UNRESERVED + ) + buffer << "#{encoded_key}=#{encoded_value}&" + end + end + self.query = buffer.chop + end + + ## + # The HTTP request URI for this URI. This is the path and the + # query string. + # + # @return [String] The request URI required for an HTTP request. + def request_uri + return nil if self.absolute? && self.scheme !~ /^https?$/ + return ( + (!self.path.empty? ? self.path : SLASH) + + (self.query ? "?#{self.query}" : EMPTY_STR) + ) + end + + ## + # Sets the HTTP request URI for this URI. + # + # @param [String, #to_str] new_request_uri The new HTTP request URI. + def request_uri=(new_request_uri) + if !new_request_uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_request_uri.class} into String." + end + if self.absolute? && self.scheme !~ /^https?$/ + raise InvalidURIError, + "Cannot set an HTTP request URI for a non-HTTP URI." + end + new_request_uri = new_request_uri.to_str + path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1] + query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1] + path_component = path_component.to_s + path_component = (!path_component.empty? ? path_component : SLASH) + self.path = path_component + self.query = query_component + + # Reset dependant values + @uri_string = nil + @hash = nil + end + + ## + # The fragment component for this URI. + # + # @return [String] The fragment component. + def fragment + return instance_variable_defined?(:@fragment) ? @fragment : nil + end + + ## + # The fragment component for this URI, normalized. + # + # @return [String] The fragment component, normalized. + def normalized_fragment + self.fragment && @normalized_fragment ||= (begin + component = Addressable::URI.normalize_component( + self.fragment, + Addressable::URI::CharacterClasses::FRAGMENT + ) + component == "" ? nil : component + end) + end + + ## + # Sets the fragment component for this URI. + # + # @param [String, #to_str] new_fragment The new fragment component. + def fragment=(new_fragment) + if new_fragment && !new_fragment.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_fragment.class} into String." + end + @fragment = new_fragment ? new_fragment.to_str : nil + + # Reset dependant values + @normalized_fragment = nil + @uri_string = nil + @hash = nil + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # Determines if the scheme indicates an IP-based protocol. + # + # @return [TrueClass, FalseClass] + # true if the scheme indicates an IP-based protocol. + # false otherwise. + def ip_based? + if self.scheme + return URI.ip_based_schemes.include?( + self.scheme.strip.downcase) + end + return false + end + + ## + # Determines if the URI is relative. + # + # @return [TrueClass, FalseClass] + # true if the URI is relative. false + # otherwise. + def relative? + return self.scheme.nil? + end + + ## + # Determines if the URI is absolute. + # + # @return [TrueClass, FalseClass] + # true if the URI is absolute. false + # otherwise. + def absolute? + return !relative? + end + + ## + # Joins two URIs together. + # + # @param [String, Addressable::URI, #to_str] The URI to join with. + # + # @return [Addressable::URI] The joined URI. + def join(uri) + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + if !uri.kind_of?(URI) + # Otherwise, convert to a String, then parse. + uri = URI.parse(uri.to_str) + end + if uri.to_s.empty? + return self.dup + end + + joined_scheme = nil + joined_user = nil + joined_password = nil + joined_host = nil + joined_port = nil + joined_path = nil + joined_query = nil + joined_fragment = nil + + # Section 5.2.2 of RFC 3986 + if uri.scheme != nil + joined_scheme = uri.scheme + joined_user = uri.user + joined_password = uri.password + joined_host = uri.host + joined_port = uri.port + joined_path = URI.normalize_path(uri.path) + joined_query = uri.query + else + if uri.authority != nil + joined_user = uri.user + joined_password = uri.password + joined_host = uri.host + joined_port = uri.port + joined_path = URI.normalize_path(uri.path) + joined_query = uri.query + else + if uri.path == nil || uri.path.empty? + joined_path = self.path + if uri.query != nil + joined_query = uri.query + else + joined_query = self.query + end + else + if uri.path[0..0] == SLASH + joined_path = URI.normalize_path(uri.path) + else + base_path = self.path.dup + base_path = EMPTY_STR if base_path == nil + base_path = URI.normalize_path(base_path) + + # Section 5.2.3 of RFC 3986 + # + # Removes the right-most path segment from the base path. + if base_path =~ /\// + base_path.gsub!(/\/[^\/]+$/, SLASH) + else + base_path = EMPTY_STR + end + + # If the base path is empty and an authority segment has been + # defined, use a base path of SLASH + if base_path.empty? && self.authority != nil + base_path = SLASH + end + + joined_path = URI.normalize_path(base_path + uri.path) + end + joined_query = uri.query + end + joined_user = self.user + joined_password = self.password + joined_host = self.host + joined_port = self.port + end + joined_scheme = self.scheme + end + joined_fragment = uri.fragment + + return self.class.new( + :scheme => joined_scheme, + :user => joined_user, + :password => joined_password, + :host => joined_host, + :port => joined_port, + :path => joined_path, + :query => joined_query, + :fragment => joined_fragment + ) + end + alias_method :+, :join + + ## + # Destructive form of join. + # + # @param [String, Addressable::URI, #to_str] The URI to join with. + # + # @return [Addressable::URI] The joined URI. + # + # @see Addressable::URI#join + def join!(uri) + replace_self(self.join(uri)) + end + + ## + # Merges a URI with a Hash of components. + # This method has different behavior from join. Any + # components present in the hash parameter will override the + # original components. The path component is not treated specially. + # + # @param [Hash, Addressable::URI, #to_hash] The components to merge with. + # + # @return [Addressable::URI] The merged URI. + # + # @see Hash#merge + def merge(hash) + if !hash.respond_to?(:to_hash) + raise TypeError, "Can't convert #{hash.class} into Hash." + end + hash = hash.to_hash + + if hash.has_key?(:authority) + if (hash.keys & [:userinfo, :user, :password, :host, :port]).any? + raise ArgumentError, + "Cannot specify both an authority and any of the components " + + "within the authority." + end + end + if hash.has_key?(:userinfo) + if (hash.keys & [:user, :password]).any? + raise ArgumentError, + "Cannot specify both a userinfo and either the user or password." + end + end + + uri = self.class.new + uri.defer_validation do + # Bunch of crazy logic required because of the composite components + # like userinfo and authority. + uri.scheme = + hash.has_key?(:scheme) ? hash[:scheme] : self.scheme + if hash.has_key?(:authority) + uri.authority = + hash.has_key?(:authority) ? hash[:authority] : self.authority + end + if hash.has_key?(:userinfo) + uri.userinfo = + hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo + end + if !hash.has_key?(:userinfo) && !hash.has_key?(:authority) + uri.user = + hash.has_key?(:user) ? hash[:user] : self.user + uri.password = + hash.has_key?(:password) ? hash[:password] : self.password + end + if !hash.has_key?(:authority) + uri.host = + hash.has_key?(:host) ? hash[:host] : self.host + uri.port = + hash.has_key?(:port) ? hash[:port] : self.port + end + uri.path = + hash.has_key?(:path) ? hash[:path] : self.path + uri.query = + hash.has_key?(:query) ? hash[:query] : self.query + uri.fragment = + hash.has_key?(:fragment) ? hash[:fragment] : self.fragment + end + + return uri + end + + ## + # Destructive form of merge. + # + # @param [Hash, Addressable::URI, #to_hash] The components to merge with. + # + # @return [Addressable::URI] The merged URI. + # + # @see Addressable::URI#merge + def merge!(uri) + replace_self(self.merge(uri)) + end + + ## + # Returns the shortest normalized relative form of this URI that uses the + # supplied URI as a base for resolution. Returns an absolute URI if + # necessary. This is effectively the opposite of route_to. + # + # @param [String, Addressable::URI, #to_str] uri The URI to route from. + # + # @return [Addressable::URI] + # The normalized relative URI that is equivalent to the original URI. + def route_from(uri) + uri = URI.parse(uri).normalize + normalized_self = self.normalize + if normalized_self.relative? + raise ArgumentError, "Expected absolute URI, got: #{self.to_s}" + end + if uri.relative? + raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}" + end + if normalized_self == uri + return Addressable::URI.parse("##{normalized_self.fragment}") + end + components = normalized_self.to_hash + if normalized_self.scheme == uri.scheme + components[:scheme] = nil + if normalized_self.authority == uri.authority + components[:user] = nil + components[:password] = nil + components[:host] = nil + components[:port] = nil + if normalized_self.path == uri.path + components[:path] = nil + if normalized_self.query == uri.query + components[:query] = nil + end + else + if uri.path != SLASH and components[:path] + self_splitted_path = split_path(components[:path]) + uri_splitted_path = split_path(uri.path) + self_dir = self_splitted_path.shift + uri_dir = uri_splitted_path.shift + while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir + self_dir = self_splitted_path.shift + uri_dir = uri_splitted_path.shift + end + components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH) + end + end + end + end + # Avoid network-path references. + if components[:host] != nil + components[:scheme] = normalized_self.scheme + end + return Addressable::URI.new( + :scheme => components[:scheme], + :user => components[:user], + :password => components[:password], + :host => components[:host], + :port => components[:port], + :path => components[:path], + :query => components[:query], + :fragment => components[:fragment] + ) + end + + ## + # Returns the shortest normalized relative form of the supplied URI that + # uses this URI as a base for resolution. Returns an absolute URI if + # necessary. This is effectively the opposite of route_from. + # + # @param [String, Addressable::URI, #to_str] uri The URI to route to. + # + # @return [Addressable::URI] + # The normalized relative URI that is equivalent to the supplied URI. + def route_to(uri) + return URI.parse(uri).route_from(self) + end + + ## + # Returns a normalized URI object. + # + # NOTE: This method does not attempt to fully conform to specifications. + # It exists largely to correct other people's failures to read the + # specifications, and also to deal with caching issues since several + # different URIs may represent the same resource and should not be + # cached multiple times. + # + # @return [Addressable::URI] The normalized URI. + def normalize + # This is a special exception for the frequently misused feed + # URI scheme. + if normalized_scheme == "feed" + if self.to_s =~ /^feed:\/*http:\/*/ + return URI.parse( + self.to_s[/^feed:\/*(http:\/*.*)/, 1] + ).normalize + end + end + + return self.class.new( + :scheme => normalized_scheme, + :authority => normalized_authority, + :path => normalized_path, + :query => normalized_query, + :fragment => normalized_fragment + ) + end + + ## + # Destructively normalizes this URI object. + # + # @return [Addressable::URI] The normalized URI. + # + # @see Addressable::URI#normalize + def normalize! + replace_self(self.normalize) + end + + ## + # Creates a URI suitable for display to users. If semantic attacks are + # likely, the application should try to detect these and warn the user. + # See RFC 3986, + # section 7.6 for more information. + # + # @return [Addressable::URI] A URI suitable for display purposes. + def display_uri + display_uri = self.normalize + display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host) + return display_uri + end + + ## + # Returns true if the URI objects are equal. This method + # normalizes both URIs before doing the comparison, and allows comparison + # against Strings. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def ===(uri) + if uri.respond_to?(:normalize) + uri_string = uri.normalize.to_s + else + begin + uri_string = ::Addressable::URI.parse(uri).normalize.to_s + rescue InvalidURIError, TypeError + return false + end + end + return self.normalize.to_s == uri_string + end + + ## + # Returns true if the URI objects are equal. This method + # normalizes both URIs before doing the comparison. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def ==(uri) + return false unless uri.kind_of?(URI) + return self.normalize.to_s == uri.normalize.to_s + end + + ## + # Returns true if the URI objects are equal. This method + # does NOT normalize either URI before doing the comparison. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def eql?(uri) + return false unless uri.kind_of?(URI) + return self.to_s == uri.to_s + end + + ## + # A hash value that will make a URI equivalent to its normalized + # form. + # + # @return [Integer] A hash of the URI. + def hash + return @hash ||= (self.to_s.hash * -1) + end + + ## + # Clones the URI object. + # + # @return [Addressable::URI] The cloned URI. + def dup + duplicated_uri = self.class.new( + :scheme => self.scheme ? self.scheme.dup : nil, + :user => self.user ? self.user.dup : nil, + :password => self.password ? self.password.dup : nil, + :host => self.host ? self.host.dup : nil, + :port => self.port, + :path => self.path ? self.path.dup : nil, + :query => self.query ? self.query.dup : nil, + :fragment => self.fragment ? self.fragment.dup : nil + ) + return duplicated_uri + end + + ## + # Omits components from a URI. + # + # @param [Symbol] *components The components to be omitted. + # + # @return [Addressable::URI] The URI with components omitted. + # + # @example + # uri = Addressable::URI.parse("http://example.com/path?query") + # #=> # + # uri.omit(:scheme, :authority) + # #=> # + def omit(*components) + invalid_components = components - [ + :scheme, :user, :password, :userinfo, :host, :port, :authority, + :path, :query, :fragment + ] + unless invalid_components.empty? + raise ArgumentError, + "Invalid component names: #{invalid_components.inspect}." + end + duplicated_uri = self.dup + duplicated_uri.defer_validation do + components.each do |component| + duplicated_uri.send((component.to_s + "=").to_sym, nil) + end + duplicated_uri.user = duplicated_uri.normalized_user + end + duplicated_uri + end + + ## + # Destructive form of omit. + # + # @param [Symbol] *components The components to be omitted. + # + # @return [Addressable::URI] The URI with components omitted. + # + # @see Addressable::URI#omit + def omit!(*components) + replace_self(self.omit(*components)) + end + + ## + # Determines if the URI is an empty string. + # + # @return [TrueClass, FalseClass] + # Returns true if empty, false otherwise. + def empty? + return self.to_s.empty? + end + + ## + # Converts the URI to a String. + # + # @return [String] The URI's String representation. + def to_s + if self.scheme == nil && self.path != nil && !self.path.empty? && + self.path =~ NORMPATH + raise InvalidURIError, + "Cannot assemble URI string with ambiguous path: '#{self.path}'" + end + @uri_string ||= (begin + uri_string = "" + uri_string << "#{self.scheme}:" if self.scheme != nil + uri_string << "//#{self.authority}" if self.authority != nil + uri_string << self.path.to_s + uri_string << "?#{self.query}" if self.query != nil + uri_string << "##{self.fragment}" if self.fragment != nil + if uri_string.respond_to?(:force_encoding) + uri_string.force_encoding(Encoding::UTF_8) + end + uri_string + end) + end + + ## + # URI's are glorified Strings. Allow implicit conversion. + alias_method :to_str, :to_s + + ## + # Returns a Hash of the URI components. + # + # @return [Hash] The URI as a Hash of components. + def to_hash + return { + :scheme => self.scheme, + :user => self.user, + :password => self.password, + :host => self.host, + :port => self.port, + :path => self.path, + :query => self.query, + :fragment => self.fragment + } + end + + ## + # Returns a String representation of the URI object's state. + # + # @return [String] The URI object's state, as a String. + def inspect + sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s) + end + + ## + # This method allows you to make several changes to a URI simultaneously, + # which separately would cause validation errors, but in conjunction, + # are valid. The URI will be revalidated as soon as the entire block has + # been executed. + # + # @param [Proc] block + # A set of operations to perform on a given URI. + def defer_validation(&block) + raise LocalJumpError, "No block given." unless block + @validation_deferred = true + block.call() + @validation_deferred = false + validate + return nil + end + + private + SELF_REF = '.' + PARENT = '..' + + RULE_2A = /\/\.\/|\/\.$/ + RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/ + RULE_2D = /^\.\.?\/?/ + RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/ + + ## + # Resolves paths to their simplest form. + # + # @param [String] path The path to normalize. + # + # @return [String] The normalized path. + def self.normalize_path(path) + # Section 5.2.4 of RFC 3986 + + return nil if path.nil? + normalized_path = path.dup + begin + mod = nil + mod ||= normalized_path.gsub!(RULE_2A, SLASH) + + pair = normalized_path.match(RULE_2B_2C) + parent, current = pair[1], pair[2] if pair + if pair && ((parent != SELF_REF && parent != PARENT) || + (current != SELF_REF && current != PARENT)) + mod ||= normalized_path.gsub!( + Regexp.new( + "/#{Regexp.escape(parent.to_s)}/\\.\\./|" + + "(/#{Regexp.escape(current.to_s)}/\\.\\.$)" + ), SLASH + ) + end + + mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR) + # Non-standard, removes prefixed dotted segments from path. + mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH) + end until mod.nil? + + return normalized_path + end + + ## + # Ensures that the URI is valid. + def validate + return if !!@validation_deferred + if self.scheme != nil && self.ip_based? && + (self.host == nil || self.host.empty?) && + (self.path == nil || self.path.empty?) + raise InvalidURIError, + "Absolute URI missing hierarchical segment: '#{self.to_s}'" + end + if self.host == nil + if self.port != nil || + self.user != nil || + self.password != nil + raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'" + end + end + if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH && + self.authority != nil + raise InvalidURIError, + "Cannot have a relative path with an authority set: '#{self.to_s}'" + end + return nil + end + + ## + # Replaces the internal state of self with the specified URI's state. + # Used in destructive operations to avoid massive code repetition. + # + # @param [Addressable::URI] uri The URI to replace self with. + # + # @return [Addressable::URI] self. + def replace_self(uri) + # Reset dependant values + instance_variables.each do |var| + instance_variable_set(var, nil) + end + + @scheme = uri.scheme + @user = uri.user + @password = uri.password + @host = uri.host + @port = uri.port + @path = uri.path + @query = uri.query + @fragment = uri.fragment + return self + end + + ## + # Splits path string with "/"(slash). + # It is considered that there is empty string after last slash when + # path ends with slash. + # + # @param [String] path The path to split. + # + # @return [Array] An array of parts of path. + def split_path(path) + splitted = path.split(SLASH) + splitted << EMPTY_STR if path.end_with? SLASH + splitted + end + end +end diff --git a/.gems/gems/addressable-2.3.6/lib/addressable/version.rb b/.gems/gems/addressable-2.3.6/lib/addressable/version.rb new file mode 100644 index 0000000..75a9ad8 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/lib/addressable/version.rb @@ -0,0 +1,30 @@ +# encoding:utf-8 +#-- +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +# Used to prevent the class/module from being loaded more than once +if !defined?(Addressable::VERSION) + module Addressable + module VERSION + MAJOR = 2 + MINOR = 3 + TINY = 6 + + STRING = [MAJOR, MINOR, TINY].join('.') + end + end +end diff --git a/.gems/gems/addressable-2.3.6/spec/addressable/idna_spec.rb b/.gems/gems/addressable-2.3.6/spec/addressable/idna_spec.rb new file mode 100644 index 0000000..b3c778a --- /dev/null +++ b/.gems/gems/addressable-2.3.6/spec/addressable/idna_spec.rb @@ -0,0 +1,238 @@ +# coding: utf-8 +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +# Have to use RubyGems to load the idn gem. +require "rubygems" + +require "addressable/idna" + +shared_examples_for "converting from unicode to ASCII" do + it "should convert 'www.google.com' correctly" do + Addressable::IDNA.to_ascii("www.google.com").should == "www.google.com" + end + + it "should convert 'www.詹姆斯.com' correctly" do + Addressable::IDNA.to_ascii( + "www.詹姆斯.com" + ).should == "www.xn--8ws00zhy3a.com" + end + + it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do + "www.Iñtërnâtiônàlizætiøn.com" + Addressable::IDNA.to_ascii( + "www.I\xC3\xB1t\xC3\xABrn\xC3\xA2ti\xC3\xB4" + + "n\xC3\xA0liz\xC3\xA6ti\xC3\xB8n.com" + ).should == "www.xn--itrntinliztin-vdb0a5exd8ewcye.com" + end + + it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do + Addressable::IDNA.to_ascii( + "www.In\xCC\x83te\xCC\x88rna\xCC\x82tio\xCC\x82n" + + "a\xCC\x80liz\xC3\xA6ti\xC3\xB8n.com" + ).should == "www.xn--itrntinliztin-vdb0a5exd8ewcye.com" + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + Addressable::IDNA.to_ascii( + "www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" + + "\201\252\343\201\214\343\201\204\343\202\217\343\201\221\343\201\256" + + "\343\202\217\343\201\213\343\202\211\343\201\252\343\201\204\343\201" + + "\251\343\202\201\343\201\204\343\202\223\343\202\201\343\201\204\343" + + "\201\256\343\202\211\343\201\271\343\202\213\343\201\276\343\201\240" + + "\343\201\252\343\201\214\343\201\217\343\201\227\343\201\252\343\201" + + "\204\343\201\250\343\201\237\343\202\212\343\201\252\343\201\204." + + "w3.mag.keio.ac.jp" + ).should == + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + Addressable::IDNA.to_ascii( + "www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" + + "\201\252\343\201\213\343\202\231\343\201\204\343\202\217\343\201\221" + + "\343\201\256\343\202\217\343\201\213\343\202\211\343\201\252\343\201" + + "\204\343\201\250\343\202\231\343\202\201\343\201\204\343\202\223\343" + + "\202\201\343\201\204\343\201\256\343\202\211\343\201\270\343\202\231" + + "\343\202\213\343\201\276\343\201\237\343\202\231\343\201\252\343\201" + + "\213\343\202\231\343\201\217\343\201\227\343\201\252\343\201\204\343" + + "\201\250\343\201\237\343\202\212\343\201\252\343\201\204." + + "w3.mag.keio.ac.jp" + ).should == + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + end + + it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do + Addressable::IDNA.to_ascii( + "点心和烤鸭.w3.mag.keio.ac.jp" + ).should == "xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp" + end + + it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do + Addressable::IDNA.to_ascii( + "가각갂갃간갅갆갇갈갉힢힣.com" + ).should == "xn--o39acdefghijk5883jma.com" + end + + it "should convert " + + "'\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com' correctly" do + Addressable::IDNA.to_ascii( + "\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com" + ).should == "xn--9cs565brid46mda086o.com" + end + + it "should convert 'リ宠퐱〹.com' correctly" do + Addressable::IDNA.to_ascii( + "\357\276\230\345\256\240\355\220\261\343\200\271.com" + ).should == "xn--eek174hoxfpr4k.com" + end + + it "should convert 'リ宠퐱卄.com' correctly" do + Addressable::IDNA.to_ascii( + "\343\203\252\345\256\240\355\220\261\345\215\204.com" + ).should == "xn--eek174hoxfpr4k.com" + end + + it "should convert 'ᆵ' correctly" do + Addressable::IDNA.to_ascii( + "\341\206\265" + ).should == "xn--4ud" + end + + it "should convert 'ᆵ' correctly" do + Addressable::IDNA.to_ascii( + "\357\276\257" + ).should == "xn--4ud" + end +end + +shared_examples_for "converting from ASCII to unicode" do + it "should convert 'www.google.com' correctly" do + Addressable::IDNA.to_unicode("www.google.com").should == "www.google.com" + end + + it "should convert 'www.詹姆斯.com' correctly" do + Addressable::IDNA.to_unicode( + "www.xn--8ws00zhy3a.com" + ).should == "www.詹姆斯.com" + end + + it "should convert 'www.iñtërnâtiônàlizætiøn.com' correctly" do + Addressable::IDNA.to_unicode( + "www.xn--itrntinliztin-vdb0a5exd8ewcye.com" + ).should == "www.iñtërnâtiônàlizætiøn.com" + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + Addressable::IDNA.to_unicode( + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + ).should == + "www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp" + end + + it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do + Addressable::IDNA.to_unicode( + "xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp" + ).should == "点心和烤鸭.w3.mag.keio.ac.jp" + end + + it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do + Addressable::IDNA.to_unicode( + "xn--o39acdefghijk5883jma.com" + ).should == "가각갂갃간갅갆갇갈갉힢힣.com" + end + + it "should convert " + + "'\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com' correctly" do + Addressable::IDNA.to_unicode( + "xn--9cs565brid46mda086o.com" + ).should == + "\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com" + end + + it "should convert 'リ宠퐱卄.com' correctly" do + Addressable::IDNA.to_unicode( + "xn--eek174hoxfpr4k.com" + ).should == "\343\203\252\345\256\240\355\220\261\345\215\204.com" + end + + it "should convert 'ᆵ' correctly" do + Addressable::IDNA.to_unicode( + "xn--4ud" + ).should == "\341\206\265" + end + + it "should normalize 'string' correctly" do + Addressable::IDNA.unicode_normalize_kc(:'string').should == "string" + Addressable::IDNA.unicode_normalize_kc("string").should == "string" + end +end + +describe Addressable::IDNA, "when using the pure-Ruby implementation" do + before do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/pure.rb" + end + + it_should_behave_like "converting from unicode to ASCII" + it_should_behave_like "converting from ASCII to unicode" + + begin + require "fiber" + + it "should not blow up inside fibers" do + f = Fiber.new do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/pure.rb" + end + f.resume + end + rescue LoadError + # Fibers aren't supported in this version of Ruby, skip this test. + warn('Fibers unsupported.') + end +end + +begin + require "idn" + + describe Addressable::IDNA, "when using the native-code implementation" do + before do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/native.rb" + end + + it_should_behave_like "converting from unicode to ASCII" + it_should_behave_like "converting from ASCII to unicode" + end +rescue LoadError + # Cannot test the native implementation without libidn support. + warn('Could not load native IDN implementation.') +end diff --git a/.gems/gems/addressable-2.3.6/spec/addressable/net_http_compat_spec.rb b/.gems/gems/addressable-2.3.6/spec/addressable/net_http_compat_spec.rb new file mode 100644 index 0000000..87a3bdd --- /dev/null +++ b/.gems/gems/addressable-2.3.6/spec/addressable/net_http_compat_spec.rb @@ -0,0 +1,28 @@ +# coding: utf-8 +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" +require "net/http" + +describe Net::HTTP do + it "should be compatible with Addressable" do + response_body = + Net::HTTP.get(Addressable::URI.parse('http://www.google.com/')) + response_body.should_not be_nil + end +end diff --git a/.gems/gems/addressable-2.3.6/spec/addressable/template_spec.rb b/.gems/gems/addressable-2.3.6/spec/addressable/template_spec.rb new file mode 100644 index 0000000..759c19d --- /dev/null +++ b/.gems/gems/addressable-2.3.6/spec/addressable/template_spec.rb @@ -0,0 +1,1328 @@ +# coding: utf-8 +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/template" + +shared_examples_for 'expands' do |tests| + tests.each do |template, expansion| + exp = expansion.is_a?(Array) ? expansion.first : expansion + it "#{template} to #{exp}" do + tmpl = Addressable::Template.new(template).expand(subject) + if expansion.is_a?(Array) + expansion.any?{|i| i == tmpl.to_str}.should be_true + else + tmpl.to_str.should == expansion + end + end + end +end + +describe "eql?" do + let(:template) { Addressable::Template.new('https://www.example.com/{foo}') } + it 'is equal when the pattern matches' do + other_template = Addressable::Template.new('https://www.example.com/{foo}') + expect(template).to be_eql(other_template) + expect(other_template).to be_eql(template) + end + it 'is not equal when the pattern differs' do + other_template = Addressable::Template.new('https://www.example.com/{bar}') + expect(template).to_not be_eql(other_template) + expect(other_template).to_not be_eql(template) + end + it 'is not equal to non-templates' do + uri = 'https://www.example.com/foo/bar' + addressable_template = Addressable::Template.new uri + addressable_uri = Addressable::URI.parse uri + expect(addressable_template).to_not be_eql(addressable_uri) + expect(addressable_uri).to_not be_eql(addressable_template) + end +end + +describe "==" do + let(:template) { Addressable::Template.new('https://www.example.com/{foo}') } + it 'is equal when the pattern matches' do + other_template = Addressable::Template.new('https://www.example.com/{foo}') + expect(template).should == other_template + expect(other_template).should == template + end + it 'is not equal when the pattern differs' do + other_template = Addressable::Template.new('https://www.example.com/{bar}') + expect(template).should_not == other_template + expect(other_template).should_not == template + end + it 'is not equal to non-templates' do + uri = 'https://www.example.com/foo/bar' + addressable_template = Addressable::Template.new uri + addressable_uri = Addressable::URI.parse uri + expect(addressable_template).should_not == addressable_uri + expect(addressable_uri).should_not == addressable_template + end +end + +describe "Type conversion" do + require "bigdecimal" + + subject { + { + :var => true, + :hello => 1234, + :nothing => nil, + :sym => :symbolic, + :decimal => BigDecimal.new(1) + } + } + + it_behaves_like 'expands', { + '{var}' => 'true', + '{hello}' => '1234', + '{nothing}' => '', + '{sym}' => 'symbolic', + '{decimal}' => '0.1E1' + } +end + +describe "Level 1:" do + subject { + {:var => "value", :hello => "Hello World!"} + } + it_behaves_like 'expands', { + '{var}' => 'value', + '{hello}' => 'Hello%20World%21' + } +end + +describe "Level 2" do + subject { + { + :var => "value", + :hello => "Hello World!", + :path => "/foo/bar" + } + } + context "Operator +:" do + it_behaves_like 'expands', { + '{+var}' => 'value', + '{+hello}' => 'Hello%20World!', + '{+path}/here' => '/foo/bar/here', + 'here?ref={+path}' => 'here?ref=/foo/bar' + } + end + context "Operator #:" do + it_behaves_like 'expands', { + 'X{#var}' => 'X#value', + 'X{#hello}' => 'X#Hello%20World!' + } + end +end + +describe "Level 3" do + subject { + { + :var => "value", + :hello => "Hello World!", + :empty => "", + :path => "/foo/bar", + :x => "1024", + :y => "768" + } + } + context "Operator nil (multiple vars):" do + it_behaves_like 'expands', { + 'map?{x,y}' => 'map?1024,768', + '{x,hello,y}' => '1024,Hello%20World%21,768' + } + end + context "Operator + (multiple vars):" do + it_behaves_like 'expands', { + '{+x,hello,y}' => '1024,Hello%20World!,768', + '{+path,x}/here' => '/foo/bar,1024/here' + } + end + context "Operator # (multiple vars):" do + it_behaves_like 'expands', { + '{#x,hello,y}' => '#1024,Hello%20World!,768', + '{#path,x}/here' => '#/foo/bar,1024/here' + } + end + context "Operator ." do + it_behaves_like 'expands', { + 'X{.var}' => 'X.value', + 'X{.x,y}' => 'X.1024.768' + } + end + context "Operator /" do + it_behaves_like 'expands', { + '{/var}' => '/value', + '{/var,x}/here' => '/value/1024/here' + } + end + context "Operator ;" do + it_behaves_like 'expands', { + '{;x,y}' => ';x=1024;y=768', + '{;x,y,empty}' => ';x=1024;y=768;empty' + } + end + context "Operator ?" do + it_behaves_like 'expands', { + '{?x,y}' => '?x=1024&y=768', + '{?x,y,empty}' => '?x=1024&y=768&empty=' + } + end + context "Operator &" do + it_behaves_like 'expands', { + '?fixed=yes{&x}' => '?fixed=yes&x=1024', + '{&x,y,empty}' => '&x=1024&y=768&empty=' + } + end +end + +describe "Level 4" do + subject { + { + :var => "value", + :hello => "Hello World!", + :path => "/foo/bar", + :semi => ";", + :list => %w(red green blue), + :keys => {"semi" => ';', "dot" => '.', "comma" => ','} + } + } + context "Expansion with value modifiers" do + it_behaves_like 'expands', { + '{var:3}' => 'val', + '{var:30}' => 'value', + '{list}' => 'red,green,blue', + '{list*}' => 'red,green,blue', + '{keys}' => [ + 'semi,%3B,dot,.,comma,%2C', + 'dot,.,semi,%3B,comma,%2C', + 'comma,%2C,semi,%3B,dot,.', + 'semi,%3B,comma,%2C,dot,.', + 'dot,.,comma,%2C,semi,%3B', + 'comma,%2C,dot,.,semi,%3B' + ], + '{keys*}' => [ + 'semi=%3B,dot=.,comma=%2C', + 'dot=.,semi=%3B,comma=%2C', + 'comma=%2C,semi=%3B,dot=.', + 'semi=%3B,comma=%2C,dot=.', + 'dot=.,comma=%2C,semi=%3B', + 'comma=%2C,dot=.,semi=%3B' + ] + } + end + context "Operator + with value modifiers" do + it_behaves_like 'expands', { + '{+path:6}/here' => '/foo/b/here', + '{+list}' => 'red,green,blue', + '{+list*}' => 'red,green,blue', + '{+keys}' => [ + 'semi,;,dot,.,comma,,', + 'dot,.,semi,;,comma,,', + 'comma,,,semi,;,dot,.', + 'semi,;,comma,,,dot,.', + 'dot,.,comma,,,semi,;', + 'comma,,,dot,.,semi,;' + ], + '{+keys*}' => [ + 'semi=;,dot=.,comma=,', + 'dot=.,semi=;,comma=,', + 'comma=,,semi=;,dot=.', + 'semi=;,comma=,,dot=.', + 'dot=.,comma=,,semi=;', + 'comma=,,dot=.,semi=;' + ] + } + end + context "Operator # with value modifiers" do + it_behaves_like 'expands', { + '{#path:6}/here' => '#/foo/b/here', + '{#list}' => '#red,green,blue', + '{#list*}' => '#red,green,blue', + '{#keys}' => [ + '#semi,;,dot,.,comma,,', + '#dot,.,semi,;,comma,,', + '#comma,,,semi,;,dot,.', + '#semi,;,comma,,,dot,.', + '#dot,.,comma,,,semi,;', + '#comma,,,dot,.,semi,;' + ], + '{#keys*}' => [ + '#semi=;,dot=.,comma=,', + '#dot=.,semi=;,comma=,', + '#comma=,,semi=;,dot=.', + '#semi=;,comma=,,dot=.', + '#dot=.,comma=,,semi=;', + '#comma=,,dot=.,semi=;' + ] + } + end + context "Operator . with value modifiers" do + it_behaves_like 'expands', { + 'X{.var:3}' => 'X.val', + 'X{.list}' => 'X.red,green,blue', + 'X{.list*}' => 'X.red.green.blue', + 'X{.keys}' => [ + 'X.semi,%3B,dot,.,comma,%2C', + 'X.dot,.,semi,%3B,comma,%2C', + 'X.comma,%2C,semi,%3B,dot,.', + 'X.semi,%3B,comma,%2C,dot,.', + 'X.dot,.,comma,%2C,semi,%3B', + 'X.comma,%2C,dot,.,semi,%3B' + ], + 'X{.keys*}' => [ + 'X.semi=%3B.dot=..comma=%2C', + 'X.dot=..semi=%3B.comma=%2C', + 'X.comma=%2C.semi=%3B.dot=.', + 'X.semi=%3B.comma=%2C.dot=.', + 'X.dot=..comma=%2C.semi=%3B', + 'X.comma=%2C.dot=..semi=%3B' + ] + } + end + context "Operator / with value modifiers" do + it_behaves_like 'expands', { + '{/var:1,var}' => '/v/value', + '{/list}' => '/red,green,blue', + '{/list*}' => '/red/green/blue', + '{/list*,path:4}' => '/red/green/blue/%2Ffoo', + '{/keys}' => [ + '/semi,%3B,dot,.,comma,%2C', + '/dot,.,semi,%3B,comma,%2C', + '/comma,%2C,semi,%3B,dot,.', + '/semi,%3B,comma,%2C,dot,.', + '/dot,.,comma,%2C,semi,%3B', + '/comma,%2C,dot,.,semi,%3B' + ], + '{/keys*}' => [ + '/semi=%3B/dot=./comma=%2C', + '/dot=./semi=%3B/comma=%2C', + '/comma=%2C/semi=%3B/dot=.', + '/semi=%3B/comma=%2C/dot=.', + '/dot=./comma=%2C/semi=%3B', + '/comma=%2C/dot=./semi=%3B' + ] + } + end + context "Operator ; with value modifiers" do + it_behaves_like 'expands', { + '{;hello:5}' => ';hello=Hello', + '{;list}' => ';list=red,green,blue', + '{;list*}' => ';list=red;list=green;list=blue', + '{;keys}' => [ + ';keys=semi,%3B,dot,.,comma,%2C', + ';keys=dot,.,semi,%3B,comma,%2C', + ';keys=comma,%2C,semi,%3B,dot,.', + ';keys=semi,%3B,comma,%2C,dot,.', + ';keys=dot,.,comma,%2C,semi,%3B', + ';keys=comma,%2C,dot,.,semi,%3B' + ], + '{;keys*}' => [ + ';semi=%3B;dot=.;comma=%2C', + ';dot=.;semi=%3B;comma=%2C', + ';comma=%2C;semi=%3B;dot=.', + ';semi=%3B;comma=%2C;dot=.', + ';dot=.;comma=%2C;semi=%3B', + ';comma=%2C;dot=.;semi=%3B' + ] + } + end + context "Operator ? with value modifiers" do + it_behaves_like 'expands', { + '{?var:3}' => '?var=val', + '{?list}' => '?list=red,green,blue', + '{?list*}' => '?list=red&list=green&list=blue', + '{?keys}' => [ + '?keys=semi,%3B,dot,.,comma,%2C', + '?keys=dot,.,semi,%3B,comma,%2C', + '?keys=comma,%2C,semi,%3B,dot,.', + '?keys=semi,%3B,comma,%2C,dot,.', + '?keys=dot,.,comma,%2C,semi,%3B', + '?keys=comma,%2C,dot,.,semi,%3B' + ], + '{?keys*}' => [ + '?semi=%3B&dot=.&comma=%2C', + '?dot=.&semi=%3B&comma=%2C', + '?comma=%2C&semi=%3B&dot=.', + '?semi=%3B&comma=%2C&dot=.', + '?dot=.&comma=%2C&semi=%3B', + '?comma=%2C&dot=.&semi=%3B' + ] + } + end + context "Operator & with value modifiers" do + it_behaves_like 'expands', { + '{&var:3}' => '&var=val', + '{&list}' => '&list=red,green,blue', + '{&list*}' => '&list=red&list=green&list=blue', + '{&keys}' => [ + '&keys=semi,%3B,dot,.,comma,%2C', + '&keys=dot,.,semi,%3B,comma,%2C', + '&keys=comma,%2C,semi,%3B,dot,.', + '&keys=semi,%3B,comma,%2C,dot,.', + '&keys=dot,.,comma,%2C,semi,%3B', + '&keys=comma,%2C,dot,.,semi,%3B' + ], + '{&keys*}' => [ + '&semi=%3B&dot=.&comma=%2C', + '&dot=.&semi=%3B&comma=%2C', + '&comma=%2C&semi=%3B&dot=.', + '&semi=%3B&comma=%2C&dot=.', + '&dot=.&comma=%2C&semi=%3B', + '&comma=%2C&dot=.&semi=%3B' + ] + } + end +end +describe "Modifiers" do + subject { + { + :var => "value", + :semi => ";", + :year => %w(1965 2000 2012), + :dom => %w(example com) + } + } + context "length" do + it_behaves_like 'expands', { + '{var:3}' => 'val', + '{var:30}' => 'value', + '{var}' => 'value', + '{semi}' => '%3B', + '{semi:2}' => '%3B' + } + end + context "explode" do + it_behaves_like 'expands', { + 'find{?year*}' => 'find?year=1965&year=2000&year=2012', + 'www{.dom*}' => 'www.example.com', + } + end +end +describe "Expansion" do + subject { + { + :count => ["one", "two", "three"], + :dom => ["example", "com"], + :dub => "me/too", + :hello => "Hello World!", + :half => "50%", + :var => "value", + :who => "fred", + :base => "http://example.com/home/", + :path => "/foo/bar", + :list => ["red", "green", "blue"], + :keys => {"semi" => ";","dot" => ".","comma" => ","}, + :v => "6", + :x => "1024", + :y => "768", + :empty => "", + :empty_keys => {}, + :undef => nil + } + } + context "concatenation" do + it_behaves_like 'expands', { + '{count}' => 'one,two,three', + '{count*}' => 'one,two,three', + '{/count}' => '/one,two,three', + '{/count*}' => '/one/two/three', + '{;count}' => ';count=one,two,three', + '{;count*}' => ';count=one;count=two;count=three', + '{?count}' => '?count=one,two,three', + '{?count*}' => '?count=one&count=two&count=three', + '{&count*}' => '&count=one&count=two&count=three' + } + end + context "simple expansion" do + it_behaves_like 'expands', { + '{var}' => 'value', + '{hello}' => 'Hello%20World%21', + '{half}' => '50%25', + 'O{empty}X' => 'OX', + 'O{undef}X' => 'OX', + '{x,y}' => '1024,768', + '{x,hello,y}' => '1024,Hello%20World%21,768', + '?{x,empty}' => '?1024,', + '?{x,undef}' => '?1024', + '?{undef,y}' => '?768', + '{var:3}' => 'val', + '{var:30}' => 'value', + '{list}' => 'red,green,blue', + '{list*}' => 'red,green,blue', + '{keys}' => [ + 'semi,%3B,dot,.,comma,%2C', + 'dot,.,semi,%3B,comma,%2C', + 'comma,%2C,semi,%3B,dot,.', + 'semi,%3B,comma,%2C,dot,.', + 'dot,.,comma,%2C,semi,%3B', + 'comma,%2C,dot,.,semi,%3B' + ], + '{keys*}' => [ + 'semi=%3B,dot=.,comma=%2C', + 'dot=.,semi=%3B,comma=%2C', + 'comma=%2C,semi=%3B,dot=.', + 'semi=%3B,comma=%2C,dot=.', + 'dot=.,comma=%2C,semi=%3B', + 'comma=%2C,dot=.,semi=%3B' + ] + } + end + context "reserved expansion (+)" do + it_behaves_like 'expands', { + '{+var}' => 'value', + '{+hello}' => 'Hello%20World!', + '{+half}' => '50%25', + '{base}index' => 'http%3A%2F%2Fexample.com%2Fhome%2Findex', + '{+base}index' => 'http://example.com/home/index', + 'O{+empty}X' => 'OX', + 'O{+undef}X' => 'OX', + '{+path}/here' => '/foo/bar/here', + 'here?ref={+path}' => 'here?ref=/foo/bar', + 'up{+path}{var}/here' => 'up/foo/barvalue/here', + '{+x,hello,y}' => '1024,Hello%20World!,768', + '{+path,x}/here' => '/foo/bar,1024/here', + '{+path:6}/here' => '/foo/b/here', + '{+list}' => 'red,green,blue', + '{+list*}' => 'red,green,blue', + '{+keys}' => [ + 'semi,;,dot,.,comma,,', + 'dot,.,semi,;,comma,,', + 'comma,,,semi,;,dot,.', + 'semi,;,comma,,,dot,.', + 'dot,.,comma,,,semi,;', + 'comma,,,dot,.,semi,;' + ], + '{+keys*}' => [ + 'semi=;,dot=.,comma=,', + 'dot=.,semi=;,comma=,', + 'comma=,,semi=;,dot=.', + 'semi=;,comma=,,dot=.', + 'dot=.,comma=,,semi=;', + 'comma=,,dot=.,semi=;' + ] + } + end + context "fragment expansion (#)" do + it_behaves_like 'expands', { + '{#var}' => '#value', + '{#hello}' => '#Hello%20World!', + '{#half}' => '#50%25', + 'foo{#empty}' => 'foo#', + 'foo{#undef}' => 'foo', + '{#x,hello,y}' => '#1024,Hello%20World!,768', + '{#path,x}/here' => '#/foo/bar,1024/here', + '{#path:6}/here' => '#/foo/b/here', + '{#list}' => '#red,green,blue', + '{#list*}' => '#red,green,blue', + '{#keys}' => [ + '#semi,;,dot,.,comma,,', + '#dot,.,semi,;,comma,,', + '#comma,,,semi,;,dot,.', + '#semi,;,comma,,,dot,.', + '#dot,.,comma,,,semi,;', + '#comma,,,dot,.,semi,;' + ], + '{#keys*}' => [ + '#semi=;,dot=.,comma=,', + '#dot=.,semi=;,comma=,', + '#comma=,,semi=;,dot=.', + '#semi=;,comma=,,dot=.', + '#dot=.,comma=,,semi=;', + '#comma=,,dot=.,semi=;' + ] + } + end + context "label expansion (.)" do + it_behaves_like 'expands', { + '{.who}' => '.fred', + '{.who,who}' => '.fred.fred', + '{.half,who}' => '.50%25.fred', + 'www{.dom*}' => 'www.example.com', + 'X{.var}' => 'X.value', + 'X{.empty}' => 'X.', + 'X{.undef}' => 'X', + 'X{.var:3}' => 'X.val', + 'X{.list}' => 'X.red,green,blue', + 'X{.list*}' => 'X.red.green.blue', + 'X{.keys}' => [ + 'X.semi,%3B,dot,.,comma,%2C', + 'X.dot,.,semi,%3B,comma,%2C', + 'X.comma,%2C,semi,%3B,dot,.', + 'X.semi,%3B,comma,%2C,dot,.', + 'X.dot,.,comma,%2C,semi,%3B', + 'X.comma,%2C,dot,.,semi,%3B' + ], + 'X{.keys*}' => [ + 'X.semi=%3B.dot=..comma=%2C', + 'X.dot=..semi=%3B.comma=%2C', + 'X.comma=%2C.semi=%3B.dot=.', + 'X.semi=%3B.comma=%2C.dot=.', + 'X.dot=..comma=%2C.semi=%3B', + 'X.comma=%2C.dot=..semi=%3B' + ], + 'X{.empty_keys}' => 'X', + 'X{.empty_keys*}' => 'X' + } + end + context "path expansion (/)" do + it_behaves_like 'expands', { + '{/who}' => '/fred', + '{/who,who}' => '/fred/fred', + '{/half,who}' => '/50%25/fred', + '{/who,dub}' => '/fred/me%2Ftoo', + '{/var}' => '/value', + '{/var,empty}' => '/value/', + '{/var,undef}' => '/value', + '{/var,x}/here' => '/value/1024/here', + '{/var:1,var}' => '/v/value', + '{/list}' => '/red,green,blue', + '{/list*}' => '/red/green/blue', + '{/list*,path:4}' => '/red/green/blue/%2Ffoo', + '{/keys}' => [ + '/semi,%3B,dot,.,comma,%2C', + '/dot,.,semi,%3B,comma,%2C', + '/comma,%2C,semi,%3B,dot,.', + '/semi,%3B,comma,%2C,dot,.', + '/dot,.,comma,%2C,semi,%3B', + '/comma,%2C,dot,.,semi,%3B' + ], + '{/keys*}' => [ + '/semi=%3B/dot=./comma=%2C', + '/dot=./semi=%3B/comma=%2C', + '/comma=%2C/semi=%3B/dot=.', + '/semi=%3B/comma=%2C/dot=.', + '/dot=./comma=%2C/semi=%3B', + '/comma=%2C/dot=./semi=%3B' + ] + } + end + context "path-style expansion (;)" do + it_behaves_like 'expands', { + '{;who}' => ';who=fred', + '{;half}' => ';half=50%25', + '{;empty}' => ';empty', + '{;v,empty,who}' => ';v=6;empty;who=fred', + '{;v,bar,who}' => ';v=6;who=fred', + '{;x,y}' => ';x=1024;y=768', + '{;x,y,empty}' => ';x=1024;y=768;empty', + '{;x,y,undef}' => ';x=1024;y=768', + '{;hello:5}' => ';hello=Hello', + '{;list}' => ';list=red,green,blue', + '{;list*}' => ';list=red;list=green;list=blue', + '{;keys}' => [ + ';keys=semi,%3B,dot,.,comma,%2C', + ';keys=dot,.,semi,%3B,comma,%2C', + ';keys=comma,%2C,semi,%3B,dot,.', + ';keys=semi,%3B,comma,%2C,dot,.', + ';keys=dot,.,comma,%2C,semi,%3B', + ';keys=comma,%2C,dot,.,semi,%3B' + ], + '{;keys*}' => [ + ';semi=%3B;dot=.;comma=%2C', + ';dot=.;semi=%3B;comma=%2C', + ';comma=%2C;semi=%3B;dot=.', + ';semi=%3B;comma=%2C;dot=.', + ';dot=.;comma=%2C;semi=%3B', + ';comma=%2C;dot=.;semi=%3B' + ] + } + end + context "form query expansion (?)" do + it_behaves_like 'expands', { + '{?who}' => '?who=fred', + '{?half}' => '?half=50%25', + '{?x,y}' => '?x=1024&y=768', + '{?x,y,empty}' => '?x=1024&y=768&empty=', + '{?x,y,undef}' => '?x=1024&y=768', + '{?var:3}' => '?var=val', + '{?list}' => '?list=red,green,blue', + '{?list*}' => '?list=red&list=green&list=blue', + '{?keys}' => [ + '?keys=semi,%3B,dot,.,comma,%2C', + '?keys=dot,.,semi,%3B,comma,%2C', + '?keys=comma,%2C,semi,%3B,dot,.', + '?keys=semi,%3B,comma,%2C,dot,.', + '?keys=dot,.,comma,%2C,semi,%3B', + '?keys=comma,%2C,dot,.,semi,%3B' + ], + '{?keys*}' => [ + '?semi=%3B&dot=.&comma=%2C', + '?dot=.&semi=%3B&comma=%2C', + '?comma=%2C&semi=%3B&dot=.', + '?semi=%3B&comma=%2C&dot=.', + '?dot=.&comma=%2C&semi=%3B', + '?comma=%2C&dot=.&semi=%3B' + ] + } + end + context "form query expansion (&)" do + it_behaves_like 'expands', { + '{&who}' => '&who=fred', + '{&half}' => '&half=50%25', + '?fixed=yes{&x}' => '?fixed=yes&x=1024', + '{&x,y,empty}' => '&x=1024&y=768&empty=', + '{&x,y,undef}' => '&x=1024&y=768', + '{&var:3}' => '&var=val', + '{&list}' => '&list=red,green,blue', + '{&list*}' => '&list=red&list=green&list=blue', + '{&keys}' => [ + '&keys=semi,%3B,dot,.,comma,%2C', + '&keys=dot,.,semi,%3B,comma,%2C', + '&keys=comma,%2C,semi,%3B,dot,.', + '&keys=semi,%3B,comma,%2C,dot,.', + '&keys=dot,.,comma,%2C,semi,%3B', + '&keys=comma,%2C,dot,.,semi,%3B' + ], + '{&keys*}' => [ + '&semi=%3B&dot=.&comma=%2C', + '&dot=.&semi=%3B&comma=%2C', + '&comma=%2C&semi=%3B&dot=.', + '&semi=%3B&comma=%2C&dot=.', + '&dot=.&comma=%2C&semi=%3B', + '&comma=%2C&dot=.&semi=%3B' + ] + } + end +end + +class ExampleTwoProcessor + def self.restore(name, value) + return value.gsub(/-/, " ") if name == "query" + return value + end + + def self.match(name) + return ".*?" if name == "first" + return ".*" + end + def self.validate(name, value) + return !!(value =~ /^[\w ]+$/) if name == "query" + return true + end + + def self.transform(name, value) + return value.gsub(/ /, "+") if name == "query" + return value + end +end + +class DumbProcessor + def self.match(name) + return ".*?" if name == "first" + end +end + +describe Addressable::Template do + describe "Matching" do + let(:uri){ + Addressable::URI.parse( + "http://example.com/search/an-example-search-query/" + ) + } + let(:uri2){ + Addressable::URI.parse("http://example.com/a/b/c/") + } + let(:uri3){ + Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo") + } + let(:uri4){ + Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo") + } + let(:uri5){ + "http://example.com/foo" + } + context "first uri with ExampleTwoProcessor" do + subject { + match = Addressable::Template.new( + "http://example.com/search/{query}/" + ).match(uri, ExampleTwoProcessor) + } + its(:variables){ should == ["query"] } + its(:captures){ should == ["an example search query"] } + end + + context "second uri with ExampleTwoProcessor" do + subject { + match = Addressable::Template.new( + "http://example.com/{first}/{+second}/" + ).match(uri2, ExampleTwoProcessor) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", "b/c"] } + end + + context "second uri with DumbProcessor" do + subject { + match = Addressable::Template.new( + "http://example.com/{first}/{+second}/" + ).match(uri2, DumbProcessor) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", "b/c"] } + end + + context "second uri" do + subject { + match = Addressable::Template.new( + "http://example.com/{first}{/second*}/" + ).match(uri2) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", ["b","c"]] } + end + context "third uri" do + subject { + match = Addressable::Template.new( + "http://example.com/{;hash*,first}" + ).match(uri3) + } + its(:variables){ should == ["hash", "first"] } + its(:captures){ should == [ + {"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] } + end + # Note that this expansion is impossible to revert deterministically - the + # * operator means first could have been a key of hash or a separate key. + # Semantically, a separate key is more likely, but both are possible. + context "fourth uri" do + subject { + match = Addressable::Template.new( + "http://example.com/{?hash*,first}" + ).match(uri4) + } + its(:variables){ should == ["hash", "first"] } + its(:captures){ should == [ + {"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] } + end + context "fifth uri" do + subject { + match = Addressable::Template.new( + "http://example.com/{path}{?hash*,first}" + ).match(uri5) + } + its(:variables){ should == ["path", "hash", "first"] } + its(:captures){ should == ["foo", nil, nil] } + end + end + describe "extract" do + let(:template) { + Addressable::Template.new( + "http://{host}{/segments*}/{?one,two,bogus}{#fragment}" + ) + } + let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" } + let(:uri2){ "http://example.com/a/b/c/#foo" } + it "should be able to extract with queries" do + template.extract(uri).should == { + "host" => "example.com", + "segments" => %w(a b c), + "one" => "1", + "bogus" => nil, + "two" => "2", + "fragment" => "foo" + } + end + it "should be able to extract without queries" do + template.extract(uri2).should == { + "host" => "example.com", + "segments" => %w(a b c), + "one" => nil, + "bogus" => nil, + "two" => nil, + "fragment" => "foo" + } + end + + context "issue #137" do + subject { Addressable::Template.new('/path{?page,per_page}') } + + it "can match empty" do + data = subject.extract("/path") + data["page"].should == nil + data["per_page"].should == nil + data.keys.sort.should == ['page', 'per_page'] + end + + it "can match first var" do + data = subject.extract("/path?page=1") + data["page"].should == "1" + data["per_page"].should == nil + data.keys.sort.should == ['page', 'per_page'] + end + + it "can match second var" do + data = subject.extract("/path?per_page=1") + data["page"].should == nil + data["per_page"].should == "1" + data.keys.sort.should == ['page', 'per_page'] + end + + it "can match both vars" do + data = subject.extract("/path?page=2&per_page=1") + data["page"].should == "2" + data["per_page"].should == "1" + data.keys.sort.should == ['page', 'per_page'] + end + end + end + + describe "Partial expand with symbols" do + context "partial_expand with two simple values" do + subject { + Addressable::Template.new("http://example.com/{one}/{two}/") + } + it "builds a new pattern" do + subject.partial_expand(:one => "1").pattern.should == + "http://example.com/1/{two}/" + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + subject.partial_expand(:one => "1", :three => "3").pattern.should == + "http://example.com/?one=1{&two}&three=3/" + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + subject.partial_expand(:one => "1").pattern.should == + "http://example.com/{?two}&one=1/" + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + subject.partial_expand(:one => "1").pattern.should == + "http://example.com/1{/two}/" + end + end + end + describe "Partial expand with strings" do + context "partial_expand with two simple values" do + subject { + Addressable::Template.new("http://example.com/{one}/{two}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1").pattern.should == + "http://example.com/1/{two}/" + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1", "three" => "3").pattern.should == + "http://example.com/?one=1{&two}&three=3/" + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1").pattern.should == + "http://example.com/{?two}&one=1/" + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1").pattern.should == + "http://example.com/1{/two}/" + end + end + end + describe "Expand" do + context "expand with a processor" do + subject { + Addressable::Template.new("http://example.com/search/{query}/") + } + it "processes spaces" do + subject.expand({"query" => "an example search query"}, + ExampleTwoProcessor).to_str.should == + "http://example.com/search/an+example+search+query/" + end + it "validates" do + lambda{ + subject.expand({"query" => "Bogus!"}, + ExampleTwoProcessor).to_str + }.should raise_error(Addressable::Template::InvalidTemplateValueError) + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1", "three" => "3").pattern.should == + "http://example.com/?one=1{&two}&three=3/" + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1").pattern.should == + "http://example.com/{?two}&one=1/" + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + subject.partial_expand("one" => "1").pattern.should == + "http://example.com/1{/two}/" + end + end + end + context "Matching with operators" do + describe "Level 1:" do + subject { Addressable::Template.new("foo{foo}/{bar}baz") } + it "can match" do + data = subject.match("foofoo/bananabaz") + data.mapping["foo"].should == "foo" + data.mapping["bar"].should == "banana" + end + it "can fail" do + subject.match("bar/foo").should be_nil + subject.match("foobaz").should be_nil + end + it "can match empty" do + data = subject.match("foo/baz") + data.mapping["foo"].should == nil + data.mapping["bar"].should == nil + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + + describe "Level 2:" do + subject { Addressable::Template.new("foo{+foo}{#bar}baz") } + it "can match" do + data = subject.match("foo/test/banana#bazbaz") + data.mapping["foo"].should == "/test/banana" + data.mapping["bar"].should == "baz" + end + it "can match empty level 2 #" do + data = subject.match("foo/test/bananabaz") + data.mapping["foo"].should == "/test/banana" + data.mapping["bar"].should == nil + data = subject.match("foo/test/banana#baz") + data.mapping["foo"].should == "/test/banana" + data.mapping["bar"].should == "" + end + it "can match empty level 2 +" do + data = subject.match("foobaz") + data.mapping["foo"].should == nil + data.mapping["bar"].should == nil + data = subject.match("foo#barbaz") + data.mapping["foo"].should == nil + data.mapping["bar"].should == "bar" + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + + describe "Level 3:" do + context "no operator" do + subject { Addressable::Template.new("foo{foo,bar}baz") } + it "can match" do + data = subject.match("foofoo,barbaz") + data.mapping["foo"].should == "foo" + data.mapping["bar"].should == "bar" + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + context "+ operator" do + subject { Addressable::Template.new("foo{+foo,bar}baz") } + it "can match" do + data = subject.match("foofoo/bar,barbaz") + data.mapping["bar"].should == "foo/bar,bar" + data.mapping["foo"].should == "" + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + context ". operator" do + subject { Addressable::Template.new("foo{.foo,bar}baz") } + it "can match" do + data = subject.match("foo.foo.barbaz") + data.mapping["foo"].should == "foo" + data.mapping["bar"].should == "bar" + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + context "/ operator" do + subject { Addressable::Template.new("foo{/foo,bar}baz") } + it "can match" do + data = subject.match("foo/foo/barbaz") + data.mapping["foo"].should == "foo" + data.mapping["bar"].should == "bar" + end + it "lists vars" do + subject.variables.should == ["foo", "bar"] + end + end + context "; operator" do + subject { Addressable::Template.new("foo{;foo,bar,baz}baz") } + it "can match" do + data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz") + data.mapping["foo"].should == "bar baz" + data.mapping["bar"].should == "foo" + data.mapping["baz"].should == "" + end + it "lists vars" do + subject.variables.should == %w(foo bar baz) + end + end + context "? operator" do + context "test" do + subject { Addressable::Template.new("foo{?foo,bar}baz") } + it "can match" do + data = subject.match("foo?foo=bar%20baz&bar=foobaz") + data.mapping["foo"].should == "bar baz" + data.mapping["bar"].should == "foo" + end + it "lists vars" do + subject.variables.should == %w(foo bar) + end + end + + context "issue #137" do + subject { Addressable::Template.new('/path{?page,per_page}') } + + it "can match empty" do + data = subject.match("/path") + data.mapping["page"].should == nil + data.mapping["per_page"].should == nil + data.mapping.keys.sort.should == ['page', 'per_page'] + end + + it "can match first var" do + data = subject.match("/path?page=1") + data.mapping["page"].should == "1" + data.mapping["per_page"].should == nil + data.mapping.keys.sort.should == ['page', 'per_page'] + end + + it "can match second var" do + data = subject.match("/path?per_page=1") + data.mapping["page"].should == nil + data.mapping["per_page"].should == "1" + data.mapping.keys.sort.should == ['page', 'per_page'] + end + + it "can match both vars" do + data = subject.match("/path?page=2&per_page=1") + data.mapping["page"].should == "2" + data.mapping["per_page"].should == "1" + data.mapping.keys.sort.should == ['page', 'per_page'] + end + end + + context "issue #71" do + subject { Addressable::Template.new("http://cyberscore.dev/api/users{?username}") } + it "can match" do + data = subject.match("http://cyberscore.dev/api/users?username=foobaz") + data.mapping["username"].should == "foobaz" + end + it "lists vars" do + subject.variables.should == %w(username) + subject.keys.should == %w(username) + end + end + end + context "& operator" do + subject { Addressable::Template.new("foo{&foo,bar}baz") } + it "can match" do + data = subject.match("foo&foo=bar%20baz&bar=foobaz") + data.mapping["foo"].should == "bar baz" + data.mapping["bar"].should == "foo" + end + it "lists vars" do + subject.variables.should == %w(foo bar) + end + end + end + end + + context "support regexes:" do + context "EXPRESSION" do + subject { Addressable::Template::EXPRESSION } + it "should be able to match an expression" do + subject.should match("{foo}") + subject.should match("{foo,9}") + subject.should match("{foo.bar,baz}") + subject.should match("{+foo.bar,baz}") + subject.should match("{foo,foo%20bar}") + subject.should match("{#foo:20,baz*}") + subject.should match("stuff{#foo:20,baz*}things") + end + it "should fail on non vars" do + subject.should_not match("!{foo") + subject.should_not match("{foo.bar.}") + subject.should_not match("!{}") + end + end + context "VARNAME" do + subject { Addressable::Template::VARNAME } + it "should be able to match a variable" do + subject.should match("foo") + subject.should match("9") + subject.should match("foo.bar") + subject.should match("foo_bar") + subject.should match("foo_bar.baz") + subject.should match("foo%20bar") + subject.should match("foo%20bar.baz") + end + it "should fail on non vars" do + subject.should_not match("!foo") + subject.should_not match("foo.bar.") + subject.should_not match("foo%2%00bar") + subject.should_not match("foo_ba%r") + subject.should_not match("foo_bar*") + subject.should_not match("foo_bar:20") + end + end + context "VARIABLE_LIST" do + subject { Addressable::Template::VARIABLE_LIST } + it "should be able to match a variable list" do + subject.should match("foo,bar") + subject.should match("foo") + subject.should match("foo,bar*,baz") + subject.should match("foo.bar,bar_baz*,baz:12") + end + it "should fail on non vars" do + subject.should_not match(",foo,bar*,baz") + subject.should_not match("foo,*bar,baz") + subject.should_not match("foo,,bar*,baz") + end + end + context "VARSPEC" do + subject { Addressable::Template::VARSPEC } + it "should be able to match a variable with modifier" do + subject.should match("9:8") + subject.should match("foo.bar*") + subject.should match("foo_bar:12") + subject.should match("foo_bar.baz*") + subject.should match("foo%20bar:12") + subject.should match("foo%20bar.baz*") + end + it "should fail on non vars" do + subject.should_not match("!foo") + subject.should_not match("*foo") + subject.should_not match("fo*o") + subject.should_not match("fo:o") + subject.should_not match("foo:") + end + end + end +end + +describe Addressable::Template::MatchData do + let(:template) { Addressable::Template.new('{foo}/{bar}') } + subject(:its) { template.match('ab/cd') } + its(:uri) { should == Addressable::URI.parse('ab/cd') } + its(:template) { should == template } + its(:mapping) { should == { 'foo' => 'ab', 'bar' => 'cd' } } + its(:variables) { should == ['foo', 'bar'] } + its(:keys) { should == ['foo', 'bar'] } + its(:names) { should == ['foo', 'bar'] } + its(:values) { should == ['ab', 'cd'] } + its(:captures) { should == ['ab', 'cd'] } + its(:to_a) { should == ['ab/cd', 'ab', 'cd'] } + its(:to_s) { should == 'ab/cd' } + its(:string) { should == its.to_s } + its(:pre_match) { should == "" } + its(:post_match) { should == "" } + + describe 'values_at' do + it 'returns an array with the values' do + its.values_at(0, 2).should == ['ab/cd', 'cd'] + end + it 'allows mixing integer an string keys' do + its.values_at('foo', 1).should == ['ab', 'ab'] + end + it 'accepts unknown keys' do + its.values_at('baz', 'foo').should == [nil, 'ab'] + end + end + + describe '[]' do + context 'string key' do + it 'returns the corresponding capture' do + its['foo'].should == 'ab' + its['bar'].should == 'cd' + end + it 'returns nil for unknown keys' do + its['baz'].should be_nil + end + end + context 'symbol key' do + it 'returns the corresponding capture' do + its[:foo].should == 'ab' + its[:bar].should == 'cd' + end + it 'returns nil for unknown keys' do + its[:baz].should be_nil + end + end + context 'integer key' do + it 'returns the full URI for index 0' do + its[0].should == 'ab/cd' + end + it 'returns the corresponding capture' do + its[1].should == 'ab' + its[2].should == 'cd' + end + it 'returns nil for unknown keys' do + its[3].should be_nil + end + end + context 'other key' do + it 'raises an exception' do + expect { its[Object.new] }.to raise_error(TypeError) + end + end + context 'with length' do + it 'returns an array starting at index with given length' do + its[0, 2].should == ['ab/cd', 'ab'] + its[2, 1].should == ['cd'] + end + end + end +end diff --git a/.gems/gems/addressable-2.3.6/spec/addressable/uri_spec.rb b/.gems/gems/addressable-2.3.6/spec/addressable/uri_spec.rb new file mode 100644 index 0000000..ee45124 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/spec/addressable/uri_spec.rb @@ -0,0 +1,5940 @@ +# coding: utf-8 +# Copyright (C) 2006-2013 Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" +require "uri" + +if !"".respond_to?("force_encoding") + class String + def force_encoding(encoding) + @encoding = encoding + end + + def encoding + @encoding ||= Encoding::ASCII_8BIT + end + end + + class Encoding + def initialize(name) + @name = name + end + + def to_s + return @name + end + + UTF_8 = Encoding.new("UTF-8") + ASCII_8BIT = Encoding.new("US-ASCII") + end +end + +module Fake + module URI + class HTTP + def initialize(uri) + @uri = uri + end + + def to_s + return @uri.to_s + end + + alias :to_str :to_s + end + end +end + +describe Addressable::URI, "when created with a non-numeric port number" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:port => "bogus") + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a non-string scheme" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:scheme => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string user" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:user => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string password" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:password => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string userinfo" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:userinfo => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string host" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:host => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string authority" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:authority => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string authority" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:authority => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string path" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:path => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string query" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:query => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string fragment" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:fragment => :bogus) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a scheme but no hierarchical " + + "segment" do + it "should raise an error" do + (lambda do + Addressable::URI.parse("http:") + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with an invalid host" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:host => "") + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created from nil components" do + before do + @uri = Addressable::URI.new + end + + it "should have a nil site value" do + @uri.site.should == nil + end + + it "should have an empty path" do + @uri.path.should == "" + end + + it "should be an empty uri" do + @uri.to_s.should == "" + end + + it "should have a nil default port" do + @uri.default_port.should == nil + end + + it "should be empty" do + @uri.should be_empty + end + + it "should raise an error if the scheme is set to whitespace" do + (lambda do + @uri.scheme = "\t \n" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme is set to all digits" do + (lambda do + @uri.scheme = "123" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + (lambda do + @uri.user = "user" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + (lambda do + @uri.password = "pass" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + (lambda do + @uri.scheme = "http" + @uri.fragment = "fragment" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + (lambda do + @uri.fragment = "fragment" + @uri.scheme = "http" + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when initialized from individual components" do + before do + @uri = Addressable::URI.new( + :scheme => "http", + :user => "user", + :password => "password", + :host => "example.com", + :port => 8080, + :path => "/path", + :query => "query=value", + :fragment => "fragment" + ) + end + + it "returns 'http' for #scheme" do + @uri.scheme.should == "http" + end + + it "returns 'http' for #normalized_scheme" do + @uri.normalized_scheme.should == "http" + end + + it "returns 'user' for #user" do + @uri.user.should == "user" + end + + it "returns 'user' for #normalized_user" do + @uri.normalized_user.should == "user" + end + + it "returns 'password' for #password" do + @uri.password.should == "password" + end + + it "returns 'password' for #normalized_password" do + @uri.normalized_password.should == "password" + end + + it "returns 'user:password' for #userinfo" do + @uri.userinfo.should == "user:password" + end + + it "returns 'user:password' for #normalized_userinfo" do + @uri.normalized_userinfo.should == "user:password" + end + + it "returns 'example.com' for #host" do + @uri.host.should == "example.com" + end + + it "returns 'example.com' for #normalized_host" do + @uri.normalized_host.should == "example.com" + end + + it "returns 'user:password@example.com:8080' for #authority" do + @uri.authority.should == "user:password@example.com:8080" + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + @uri.normalized_authority.should == "user:password@example.com:8080" + end + + it "returns 8080 for #port" do + @uri.port.should == 8080 + end + + it "returns 8080 for #normalized_port" do + @uri.normalized_port.should == 8080 + end + + it "returns 80 for #default_port" do + @uri.default_port.should == 80 + end + + it "returns 'http://user:password@example.com:8080' for #site" do + @uri.site.should == "http://user:password@example.com:8080" + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + @uri.normalized_site.should == "http://user:password@example.com:8080" + end + + it "returns '/path' for #path" do + @uri.path.should == "/path" + end + + it "returns '/path' for #normalized_path" do + @uri.normalized_path.should == "/path" + end + + it "returns 'query=value' for #query" do + @uri.query.should == "query=value" + end + + it "returns 'query=value' for #normalized_query" do + @uri.normalized_query.should == "query=value" + end + + it "returns 'fragment' for #fragment" do + @uri.fragment.should == "fragment" + end + + it "returns 'fragment' for #normalized_fragment" do + @uri.normalized_fragment.should == "fragment" + end + + it "returns #hash" do + @uri.hash.should_not be nil + end + + it "returns #to_s" do + @uri.to_s.should == + "http://user:password@example.com:8080/path?query=value#fragment" + end + + it "should not be empty" do + @uri.should_not be_empty + end + + it "should not be frozen" do + @uri.should_not be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when initialized from " + + "frozen individual components" do + before do + @uri = Addressable::URI.new( + :scheme => "http".freeze, + :user => "user".freeze, + :password => "password".freeze, + :host => "example.com".freeze, + :port => "8080".freeze, + :path => "/path".freeze, + :query => "query=value".freeze, + :fragment => "fragment".freeze + ) + end + + it "returns 'http' for #scheme" do + @uri.scheme.should == "http" + end + + it "returns 'http' for #normalized_scheme" do + @uri.normalized_scheme.should == "http" + end + + it "returns 'user' for #user" do + @uri.user.should == "user" + end + + it "returns 'user' for #normalized_user" do + @uri.normalized_user.should == "user" + end + + it "returns 'password' for #password" do + @uri.password.should == "password" + end + + it "returns 'password' for #normalized_password" do + @uri.normalized_password.should == "password" + end + + it "returns 'user:password' for #userinfo" do + @uri.userinfo.should == "user:password" + end + + it "returns 'user:password' for #normalized_userinfo" do + @uri.normalized_userinfo.should == "user:password" + end + + it "returns 'example.com' for #host" do + @uri.host.should == "example.com" + end + + it "returns 'example.com' for #normalized_host" do + @uri.normalized_host.should == "example.com" + end + + it "returns 'user:password@example.com:8080' for #authority" do + @uri.authority.should == "user:password@example.com:8080" + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + @uri.normalized_authority.should == "user:password@example.com:8080" + end + + it "returns 8080 for #port" do + @uri.port.should == 8080 + end + + it "returns 8080 for #normalized_port" do + @uri.normalized_port.should == 8080 + end + + it "returns 80 for #default_port" do + @uri.default_port.should == 80 + end + + it "returns 'http://user:password@example.com:8080' for #site" do + @uri.site.should == "http://user:password@example.com:8080" + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + @uri.normalized_site.should == "http://user:password@example.com:8080" + end + + it "returns '/path' for #path" do + @uri.path.should == "/path" + end + + it "returns '/path' for #normalized_path" do + @uri.normalized_path.should == "/path" + end + + it "returns 'query=value' for #query" do + @uri.query.should == "query=value" + end + + it "returns 'query=value' for #normalized_query" do + @uri.normalized_query.should == "query=value" + end + + it "returns 'fragment' for #fragment" do + @uri.fragment.should == "fragment" + end + + it "returns 'fragment' for #normalized_fragment" do + @uri.normalized_fragment.should == "fragment" + end + + it "returns #hash" do + @uri.hash.should_not be nil + end + + it "returns #to_s" do + @uri.to_s.should == + "http://user:password@example.com:8080/path?query=value#fragment" + end + + it "should not be empty" do + @uri.should_not be_empty + end + + it "should not be frozen" do + @uri.should_not be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when parsed from a frozen string" do + before do + @uri = Addressable::URI.parse( + "http://user:password@example.com:8080/path?query=value#fragment".freeze + ) + end + + it "returns 'http' for #scheme" do + @uri.scheme.should == "http" + end + + it "returns 'http' for #normalized_scheme" do + @uri.normalized_scheme.should == "http" + end + + it "returns 'user' for #user" do + @uri.user.should == "user" + end + + it "returns 'user' for #normalized_user" do + @uri.normalized_user.should == "user" + end + + it "returns 'password' for #password" do + @uri.password.should == "password" + end + + it "returns 'password' for #normalized_password" do + @uri.normalized_password.should == "password" + end + + it "returns 'user:password' for #userinfo" do + @uri.userinfo.should == "user:password" + end + + it "returns 'user:password' for #normalized_userinfo" do + @uri.normalized_userinfo.should == "user:password" + end + + it "returns 'example.com' for #host" do + @uri.host.should == "example.com" + end + + it "returns 'example.com' for #normalized_host" do + @uri.normalized_host.should == "example.com" + end + + it "returns 'user:password@example.com:8080' for #authority" do + @uri.authority.should == "user:password@example.com:8080" + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + @uri.normalized_authority.should == "user:password@example.com:8080" + end + + it "returns 8080 for #port" do + @uri.port.should == 8080 + end + + it "returns 8080 for #normalized_port" do + @uri.normalized_port.should == 8080 + end + + it "returns 80 for #default_port" do + @uri.default_port.should == 80 + end + + it "returns 'http://user:password@example.com:8080' for #site" do + @uri.site.should == "http://user:password@example.com:8080" + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + @uri.normalized_site.should == "http://user:password@example.com:8080" + end + + it "returns '/path' for #path" do + @uri.path.should == "/path" + end + + it "returns '/path' for #normalized_path" do + @uri.normalized_path.should == "/path" + end + + it "returns 'query=value' for #query" do + @uri.query.should == "query=value" + end + + it "returns 'query=value' for #normalized_query" do + @uri.normalized_query.should == "query=value" + end + + it "returns 'fragment' for #fragment" do + @uri.fragment.should == "fragment" + end + + it "returns 'fragment' for #normalized_fragment" do + @uri.normalized_fragment.should == "fragment" + end + + it "returns #hash" do + @uri.hash.should_not be nil + end + + it "returns #to_s" do + @uri.to_s.should == + "http://user:password@example.com:8080/path?query=value#fragment" + end + + it "should not be empty" do + @uri.should_not be_empty + end + + it "should not be frozen" do + @uri.should_not be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when frozen" do + before do + @uri = Addressable::URI.new.freeze + end + + it "returns nil for #scheme" do + @uri.scheme.should == nil + end + + it "returns nil for #normalized_scheme" do + @uri.normalized_scheme.should == nil + end + + it "returns nil for #user" do + @uri.user.should == nil + end + + it "returns nil for #normalized_user" do + @uri.normalized_user.should == nil + end + + it "returns nil for #password" do + @uri.password.should == nil + end + + it "returns nil for #normalized_password" do + @uri.normalized_password.should == nil + end + + it "returns nil for #userinfo" do + @uri.userinfo.should == nil + end + + it "returns nil for #normalized_userinfo" do + @uri.normalized_userinfo.should == nil + end + + it "returns nil for #host" do + @uri.host.should == nil + end + + it "returns nil for #normalized_host" do + @uri.normalized_host.should == nil + end + + it "returns nil for #authority" do + @uri.authority.should == nil + end + + it "returns nil for #normalized_authority" do + @uri.normalized_authority.should == nil + end + + it "returns nil for #port" do + @uri.port.should == nil + end + + it "returns nil for #normalized_port" do + @uri.normalized_port.should == nil + end + + it "returns nil for #default_port" do + @uri.default_port.should == nil + end + + it "returns nil for #site" do + @uri.site.should == nil + end + + it "returns nil for #normalized_site" do + @uri.normalized_site.should == nil + end + + it "returns '' for #path" do + @uri.path.should == '' + end + + it "returns '' for #normalized_path" do + @uri.normalized_path.should == '' + end + + it "returns nil for #query" do + @uri.query.should == nil + end + + it "returns nil for #normalized_query" do + @uri.normalized_query.should == nil + end + + it "returns nil for #fragment" do + @uri.fragment.should == nil + end + + it "returns nil for #normalized_fragment" do + @uri.normalized_fragment.should == nil + end + + it "returns #hash" do + @uri.hash.should_not be nil + end + + it "returns #to_s" do + @uri.to_s.should == '' + end + + it "should be empty" do + @uri.should be_empty + end + + it "should be frozen" do + @uri.should be_frozen + end + + it "should not be frozen after duping" do + @uri.dup.should_not be_frozen + end + + it "should not allow destructive operations" do + expect { @uri.normalize! }.to raise_error { |error| + error.message.should match(/can't modify frozen/) + error.should satisfy { |e| RuntimeError === e || TypeError === e } + } + end +end + +describe Addressable::URI, "when frozen" do + before do + @uri = Addressable::URI.parse( + "HTTP://example.com.:%38%30/%70a%74%68?a=%31#1%323" + ).freeze + end + + it "returns 'HTTP' for #scheme" do + @uri.scheme.should == "HTTP" + end + + it "returns 'http' for #normalized_scheme" do + @uri.normalized_scheme.should == "http" + @uri.normalize.scheme.should == "http" + end + + it "returns nil for #user" do + @uri.user.should == nil + end + + it "returns nil for #normalized_user" do + @uri.normalized_user.should == nil + end + + it "returns nil for #password" do + @uri.password.should == nil + end + + it "returns nil for #normalized_password" do + @uri.normalized_password.should == nil + end + + it "returns nil for #userinfo" do + @uri.userinfo.should == nil + end + + it "returns nil for #normalized_userinfo" do + @uri.normalized_userinfo.should == nil + end + + it "returns 'example.com.' for #host" do + @uri.host.should == "example.com." + end + + it "returns nil for #normalized_host" do + @uri.normalized_host.should == "example.com" + @uri.normalize.host.should == "example.com" + end + + it "returns 'example.com.:80' for #authority" do + @uri.authority.should == "example.com.:80" + end + + it "returns 'example.com:80' for #normalized_authority" do + @uri.normalized_authority.should == "example.com" + @uri.normalize.authority.should == "example.com" + end + + it "returns 80 for #port" do + @uri.port.should == 80 + end + + it "returns nil for #normalized_port" do + @uri.normalized_port.should == nil + @uri.normalize.port.should == nil + end + + it "returns 80 for #default_port" do + @uri.default_port.should == 80 + end + + it "returns 'HTTP://example.com.:80' for #site" do + @uri.site.should == "HTTP://example.com.:80" + end + + it "returns 'http://example.com' for #normalized_site" do + @uri.normalized_site.should == "http://example.com" + @uri.normalize.site.should == "http://example.com" + end + + it "returns '/%70a%74%68' for #path" do + @uri.path.should == "/%70a%74%68" + end + + it "returns '/path' for #normalized_path" do + @uri.normalized_path.should == "/path" + @uri.normalize.path.should == "/path" + end + + it "returns 'a=%31' for #query" do + @uri.query.should == "a=%31" + end + + it "returns 'a=1' for #normalized_query" do + @uri.normalized_query.should == "a=1" + @uri.normalize.query.should == "a=1" + end + + it "returns '1%323' for #fragment" do + @uri.fragment.should == "1%323" + end + + it "returns '123' for #normalized_fragment" do + @uri.normalized_fragment.should == "123" + @uri.normalize.fragment.should == "123" + end + + it "returns #hash" do + @uri.hash.should_not be nil + end + + it "returns #to_s" do + @uri.to_s.should == 'HTTP://example.com.:80/%70a%74%68?a=%31#1%323' + @uri.normalize.to_s.should == 'http://example.com/path?a=1#123' + end + + it "should not be empty" do + @uri.should_not be_empty + end + + it "should be frozen" do + @uri.should be_frozen + end + + it "should not be frozen after duping" do + @uri.dup.should_not be_frozen + end + + it "should not allow destructive operations" do + expect { @uri.normalize! }.to raise_error { |error| + error.message.should match(/can't modify frozen/) + error.should satisfy { |e| RuntimeError === e || TypeError === e } + } + end +end + +describe Addressable::URI, "when created from string components" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :host => "example.com" + ) + end + + it "should have a site value of 'http://example.com'" do + @uri.site.should == "http://example.com" + end + + it "should be equal to the equivalent parsed URI" do + @uri.should == Addressable::URI.parse("http://example.com") + end + + it "should raise an error if invalid components omitted" do + (lambda do + @uri.omit(:bogus) + end).should raise_error(ArgumentError) + (lambda do + @uri.omit(:scheme, :bogus, :path) + end).should raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with a nil host but " + + "non-nil authority components" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:user => "user", :password => "pass", :port => 80) + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with both an authority and a user" do + it "should raise an error" do + (lambda do + Addressable::URI.new( + :user => "user", :authority => "user@example.com:80" + ) + end).should raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with an authority and no port" do + before do + @uri = Addressable::URI.new(:authority => "user@example.com") + end + + it "should not infer a port" do + @uri.port.should == nil + @uri.default_port.should == nil + @uri.inferred_port.should == nil + end + + it "should have a site value of '//user@example.com'" do + @uri.site.should == "//user@example.com" + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when created with a host with trailing dots" do + before do + @uri = Addressable::URI.new(:authority => "example...") + end + + it "should have a stable normalized form" do + @uri.normalize.normalize.normalize.host.should == + @uri.normalize.host + end +end + +describe Addressable::URI, "when created with both a userinfo and a user" do + it "should raise an error" do + (lambda do + Addressable::URI.new(:user => "user", :userinfo => "user:pass") + end).should raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with a path that hasn't been " + + "prefixed with a '/' but a host specified" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :host => "example.com", :path => "path" + ) + end + + it "should prefix a '/' to the path" do + @uri.should == Addressable::URI.parse("http://example.com/path") + end + + it "should have a site value of 'http://example.com'" do + @uri.site.should == "http://example.com" + end + + it "should have an origin of 'http://example.com" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when created with a path that hasn't been " + + "prefixed with a '/' but no host specified" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :path => "path" + ) + end + + it "should not prefix a '/' to the path" do + @uri.should == Addressable::URI.parse("http:path") + end + + it "should have a site value of 'http:'" do + @uri.site.should == "http:" + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from an Addressable::URI object" do + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.parse(original_uri) + new_uri.host = 'www.example.com' + new_uri.host.should == 'www.example.com' + new_uri.to_s.should == 'http://www.example.com/' + original_uri.host.should == 'example.com' + original_uri.to_s.should == 'http://example.com/' + end + + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.heuristic_parse(original_uri) + new_uri.host = 'www.example.com' + new_uri.host.should == 'www.example.com' + new_uri.to_s.should == 'http://www.example.com/' + original_uri.host.should == 'example.com' + original_uri.to_s.should == 'http://example.com/' + end +end + +describe Addressable::URI, "when parsed from something that looks " + + "like a URI object" do + it "should parse without error" do + uri = Addressable::URI.parse(Fake::URI::HTTP.new("http://example.com/")) + (lambda do + Addressable::URI.parse(uri) + end).should_not raise_error + end +end + +describe Addressable::URI, "when parsed from ''" do + before do + @uri = Addressable::URI.parse("") + end + + it "should have no scheme" do + @uri.scheme.should == nil + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of ''" do + @uri.path.should == "" + end + + it "should have a request URI of '/'" do + @uri.request_uri.should == "/" + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'ftp://ftp.is.co.za/rfc/rfc1808.txt'" do + before do + @uri = Addressable::URI.parse("ftp://ftp.is.co.za/rfc/rfc1808.txt") + end + + it "should use the 'ftp' scheme" do + @uri.scheme.should == "ftp" + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a host of 'ftp.is.co.za'" do + @uri.host.should == "ftp.is.co.za" + end + + it "should have inferred_port of 21" do + @uri.inferred_port.should == 21 + end + + it "should have a path of '/rfc/rfc1808.txt'" do + @uri.path.should == "/rfc/rfc1808.txt" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have an origin of 'ftp://ftp.is.co.za'" do + @uri.origin.should == 'ftp://ftp.is.co.za' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'http://www.ietf.org/rfc/rfc2396.txt'" do + before do + @uri = Addressable::URI.parse("http://www.ietf.org/rfc/rfc2396.txt") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a host of 'www.ietf.org'" do + @uri.host.should == "www.ietf.org" + end + + it "should have inferred_port of 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/rfc/rfc2396.txt'" do + @uri.path.should == "/rfc/rfc2396.txt" + end + + it "should have a request URI of '/rfc/rfc2396.txt'" do + @uri.request_uri.should == "/rfc/rfc2396.txt" + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should correctly omit components" do + @uri.omit(:scheme).to_s.should == "//www.ietf.org/rfc/rfc2396.txt" + @uri.omit(:path).to_s.should == "http://www.ietf.org" + end + + it "should correctly omit components destructively" do + @uri.omit!(:scheme) + @uri.to_s.should == "//www.ietf.org/rfc/rfc2396.txt" + end + + it "should have an origin of 'http://www.ietf.org'" do + @uri.origin.should == 'http://www.ietf.org' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'ldap://[2001:db8::7]/c=GB?objectClass?one'" do + before do + @uri = Addressable::URI.parse("ldap://[2001:db8::7]/c=GB?objectClass?one") + end + + it "should use the 'ldap' scheme" do + @uri.scheme.should == "ldap" + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a host of '[2001:db8::7]'" do + @uri.host.should == "[2001:db8::7]" + end + + it "should have inferred_port of 389" do + @uri.inferred_port.should == 389 + end + + it "should have a path of '/c=GB'" do + @uri.path.should == "/c=GB" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should not allow request URI assignment" do + (lambda do + @uri.request_uri = "/" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should have a query of 'objectClass?one'" do + @uri.query.should == "objectClass?one" + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should correctly omit components" do + @uri.omit(:scheme, :authority).to_s.should == "/c=GB?objectClass?one" + @uri.omit(:path).to_s.should == "ldap://[2001:db8::7]?objectClass?one" + end + + it "should correctly omit components destructively" do + @uri.omit!(:scheme, :authority) + @uri.to_s.should == "/c=GB?objectClass?one" + end + + it "should raise an error if omission would create an invalid URI" do + (lambda do + @uri.omit(:authority, :path) + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should have an origin of 'ldap://[2001:db8::7]'" do + @uri.origin.should == 'ldap://[2001:db8::7]' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'mailto:John.Doe@example.com'" do + before do + @uri = Addressable::URI.parse("mailto:John.Doe@example.com") + end + + it "should use the 'mailto' scheme" do + @uri.scheme.should == "mailto" + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should not have an inferred_port" do + @uri.inferred_port.should == nil + end + + it "should have a path of 'John.Doe@example.com'" do + @uri.path.should == "John.Doe@example.com" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +# Section 2 of RFC 6068 +describe Addressable::URI, "when parsed from " + + "'mailto:?to=addr1@an.example,addr2@an.example'" do + before do + @uri = Addressable::URI.parse( + "mailto:?to=addr1@an.example,addr2@an.example" + ) + end + + it "should use the 'mailto' scheme" do + @uri.scheme.should == "mailto" + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should not have an inferred_port" do + @uri.inferred_port.should == nil + end + + it "should have a path of ''" do + @uri.path.should == "" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should have the To: field value parameterized" do + @uri.query_values(Hash)["to"].should == ( + "addr1@an.example,addr2@an.example" + ) + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'news:comp.infosystems.www.servers.unix'" do + before do + @uri = Addressable::URI.parse("news:comp.infosystems.www.servers.unix") + end + + it "should use the 'news' scheme" do + @uri.scheme.should == "news" + end + + it "should not have an inferred_port" do + @uri.inferred_port.should == nil + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of 'comp.infosystems.www.servers.unix'" do + @uri.path.should == "comp.infosystems.www.servers.unix" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'tel:+1-816-555-1212'" do + before do + @uri = Addressable::URI.parse("tel:+1-816-555-1212") + end + + it "should use the 'tel' scheme" do + @uri.scheme.should == "tel" + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should not have an inferred_port" do + @uri.inferred_port.should == nil + end + + it "should have a path of '+1-816-555-1212'" do + @uri.path.should == "+1-816-555-1212" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'telnet://192.0.2.16:80/'" do + before do + @uri = Addressable::URI.parse("telnet://192.0.2.16:80/") + end + + it "should use the 'telnet' scheme" do + @uri.scheme.should == "telnet" + end + + it "should have a host of '192.0.2.16'" do + @uri.host.should == "192.0.2.16" + end + + it "should have a port of 80" do + @uri.port.should == 80 + end + + it "should have a inferred_port of 80" do + @uri.inferred_port.should == 80 + end + + it "should have a default_port of 23" do + @uri.default_port.should == 23 + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a path of '/'" do + @uri.path.should == "/" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have an origin of 'telnet://192.0.2.16:80'" do + @uri.origin.should == 'telnet://192.0.2.16:80' + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'urn:oasis:names:specification:docbook:dtd:xml:4.1.2'" do + before do + @uri = Addressable::URI.parse( + "urn:oasis:names:specification:docbook:dtd:xml:4.1.2") + end + + it "should use the 'urn' scheme" do + @uri.scheme.should == "urn" + end + + it "should not have an inferred_port" do + @uri.inferred_port.should == nil + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of " + + "'oasis:names:specification:docbook:dtd:xml:4.1.2'" do + @uri.path.should == "oasis:names:specification:docbook:dtd:xml:4.1.2" + end + + it "should not have a request URI" do + @uri.request_uri.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when heuristically parsed from " + + "'192.0.2.16:8000/path'" do + before do + @uri = Addressable::URI.heuristic_parse("192.0.2.16:8000/path") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a host of '192.0.2.16'" do + @uri.host.should == "192.0.2.16" + end + + it "should have a port of '8000'" do + @uri.port.should == 8000 + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a path of '/path'" do + @uri.path.should == "/path" + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have an origin of 'http://192.0.2.16:8000'" do + @uri.origin.should == 'http://192.0.2.16:8000' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com'" do + before do + @uri = Addressable::URI.parse("http://example.com") + end + + it "when inspected, should have the correct URI" do + @uri.inspect.should include("http://example.com") + end + + it "when inspected, should have the correct class name" do + @uri.inspect.should include("Addressable::URI") + end + + it "when inspected, should have the correct object id" do + @uri.inspect.should include("%#0x" % @uri.object_id) + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should be considered ip-based" do + @uri.should be_ip_based + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should not have a specified port" do + @uri.port.should == nil + end + + it "should have an empty path" do + @uri.path.should == "" + end + + it "should have no query string" do + @uri.query.should == nil + @uri.query_values.should == nil + end + + it "should have a request URI of '/'" do + @uri.request_uri.should == "/" + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should not be considered relative" do + @uri.should_not be_relative + end + + it "should not be exactly equal to 42" do + @uri.eql?(42).should == false + end + + it "should not be equal to 42" do + (@uri == 42).should == false + end + + it "should not be roughly equal to 42" do + (@uri === 42).should == false + end + + it "should be exactly equal to http://example.com" do + @uri.eql?(Addressable::URI.parse("http://example.com")).should == true + end + + it "should be roughly equal to http://example.com/" do + (@uri === Addressable::URI.parse("http://example.com/")).should == true + end + + it "should be roughly equal to the string 'http://example.com/'" do + (@uri === "http://example.com/").should == true + end + + it "should not be roughly equal to the string " + + "'http://example.com:bogus/'" do + (lambda do + (@uri === "http://example.com:bogus/").should == false + end).should_not raise_error + end + + it "should result in itself when joined with itself" do + @uri.join(@uri).to_s.should == "http://example.com" + @uri.join!(@uri).to_s.should == "http://example.com" + end + + it "should be equivalent to http://EXAMPLE.com" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.com") + end + + it "should be equivalent to http://EXAMPLE.com:80/" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.com:80/") + end + + it "should have the same hash as http://example.com" do + @uri.hash.should == Addressable::URI.parse("http://example.com").hash + end + + it "should have the same hash as http://EXAMPLE.com after assignment" do + @uri.host = "EXAMPLE.com" + @uri.hash.should == Addressable::URI.parse("http://EXAMPLE.com").hash + end + + it "should have a different hash from http://EXAMPLE.com" do + @uri.hash.should_not == Addressable::URI.parse("http://EXAMPLE.com").hash + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com/" do + @uri.should == Addressable::URI.parse("http://example.com/") + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com:/" do + @uri.should == Addressable::URI.parse("http://example.com:/") + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com:80/" do + @uri.should == Addressable::URI.parse("http://example.com:80/") + end + + # Section 6.2.2.1 of RFC 3986 + it "should be equivalent to http://EXAMPLE.COM/" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.COM/") + end + + it "should have a route of '/path/' to 'http://example.com/path/'" do + @uri.route_to("http://example.com/path/").should == + Addressable::URI.parse("/path/") + end + + it "should have a route of '..' from 'http://example.com/path/'" do + @uri.route_from("http://example.com/path/").should == + Addressable::URI.parse("..") + end + + it "should have a route of '#' to 'http://example.com/'" do + @uri.route_to("http://example.com/").should == + Addressable::URI.parse("#") + end + + it "should have a route of 'http://elsewhere.com/' to " + + "'http://elsewhere.com/'" do + @uri.route_to("http://elsewhere.com/").should == + Addressable::URI.parse("http://elsewhere.com/") + end + + it "when joined with 'relative/path' should be " + + "'http://example.com/relative/path'" do + @uri.join('relative/path').should == + Addressable::URI.parse("http://example.com/relative/path") + end + + it "when joined with a bogus object a TypeError should be raised" do + (lambda do + @uri.join(42) + end).should raise_error(TypeError) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password.should == nil + @uri.to_s.should == "http://newuser@example.com" + end + + it "should have the correct username after assignment" do + @uri.user = "user@123!" + @uri.user.should == "user@123!" + @uri.normalized_user.should == "user%40123%21" + @uri.password.should == nil + @uri.normalize.to_s.should == "http://user%40123%21@example.com/" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.user.should == "" + @uri.to_s.should == "http://:newpass@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "#secret@123!" + @uri.password.should == "#secret@123!" + @uri.normalized_password.should == "%23secret%40123%21" + @uri.user.should == "" + @uri.normalize.to_s.should == "http://:%23secret%40123%21@example.com/" + @uri.omit(:password).to_s.should == "http://example.com" + end + + it "should have the correct user/pass after repeated assignment" do + @uri.user = nil + @uri.user.should == nil + @uri.password = "newpass" + @uri.password.should == "newpass" + # Username cannot be nil if the password is set + @uri.user.should == "" + @uri.to_s.should == "http://:newpass@example.com" + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password = nil + @uri.password.should == nil + @uri.to_s.should == "http://newuser@example.com" + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password = "" + @uri.password.should == "" + @uri.to_s.should == "http://newuser:@example.com" + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.user = nil + # Username cannot be nil if the password is set + @uri.user.should == "" + @uri.to_s.should == "http://:newpass@example.com" + end + + it "should have the correct user/pass after userinfo assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.userinfo = nil + @uri.userinfo.should == nil + @uri.user.should == nil + @uri.password.should == nil + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +# Section 5.1.2 of RFC 2616 +describe Addressable::URI, "when parsed from " + + "'http://www.w3.org/pub/WWW/TheProject.html'" do + before do + @uri = Addressable::URI.parse("http://www.w3.org/pub/WWW/TheProject.html") + end + + it "should have the correct request URI" do + @uri.request_uri.should == "/pub/WWW/TheProject.html" + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "/some/where/else.html?query?string" + @uri.request_uri.should == "/some/where/else.html?query?string" + @uri.path.should == "/some/where/else.html" + @uri.query.should == "query?string" + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "?x=y" + @uri.request_uri.should == "/?x=y" + @uri.path.should == "/" + @uri.query.should == "x=y" + end + + it "should raise an error if the site value is set to something bogus" do + (lambda do + @uri.site = 42 + end).should raise_error(TypeError) + end + + it "should raise an error if the request URI is set to something bogus" do + (lambda do + @uri.request_uri = 42 + end).should raise_error(TypeError) + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "www.w3.org", + :port => nil, + :path => "/pub/WWW/TheProject.html", + :query => nil, + :fragment => nil + } + end + + it "should have an origin of 'http://www.w3.org'" do + @uri.origin.should == 'http://www.w3.org' + end +end + +describe Addressable::URI, "when parsing IPv6 addresses" do + it "should not raise an error for " + + "'http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[fe80:0:0:0:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[fe80:0:0:0:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[fe80::200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[fe80::200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[::1]/'" do + Addressable::URI.parse("http://[::1]/") + end + + it "should not raise an error for " + + "'http://[fe80::1]/'" do + Addressable::URI.parse("http://[fe80::1]/") + end + + it "should raise an error for " + + "'http://[]/'" do + (lambda do + Addressable::URI.parse("http://[]/") + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsing IPv6 address" do + subject { Addressable::URI.parse("http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") } + its(:host) { should == '[3ffe:1900:4545:3:200:f8ff:fe21:67cf]' } + its(:hostname) { should == '3ffe:1900:4545:3:200:f8ff:fe21:67cf' } +end + +describe Addressable::URI, "when assigning IPv6 address" do + it "should allow to set bare IPv6 address as hostname" do + uri = Addressable::URI.parse("http://[::1]/") + uri.hostname = '3ffe:1900:4545:3:200:f8ff:fe21:67cf' + uri.to_s.should == 'http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/' + end + + it "should not allow to set bare IPv6 address as host" do + uri = Addressable::URI.parse("http://[::1]/") + pending "not checked" + (lambda do + uri.host = '3ffe:1900:4545:3:200:f8ff:fe21:67cf' + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsing IPvFuture addresses" do + it "should not raise an error for " + + "'http://[v9.3ffe:1900:4545:3:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[v9.3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[vff.fe80:0:0:0:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[vff.fe80:0:0:0:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[v12.fe80::200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[v12.fe80::200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[va0.::1]/'" do + Addressable::URI.parse("http://[va0.::1]/") + end + + it "should not raise an error for " + + "'http://[v255.fe80::1]/'" do + Addressable::URI.parse("http://[v255.fe80::1]/") + end + + it "should raise an error for " + + "'http://[v0.]/'" do + (lambda do + Addressable::URI.parse("http://[v0.]/") + end).should raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/'" do + before do + @uri = Addressable::URI.parse("http://example.com/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com" do + @uri.should == Addressable::URI.parse("http://example.com") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to HTTP://example.com/" do + @uri.should == Addressable::URI.parse("HTTP://example.com/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com:/" do + @uri.should == Addressable::URI.parse("http://example.com:/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com:80/" do + @uri.should == Addressable::URI.parse("http://example.com:80/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://Example.com/" do + @uri.should == Addressable::URI.parse("http://Example.com/") + end + + it "should have the correct username after assignment" do + @uri.user = nil + @uri.user.should == nil + @uri.password.should == nil + @uri.to_s.should == "http://example.com/" + end + + it "should have the correct password after assignment" do + @uri.password = nil + @uri.password.should == nil + @uri.user.should == nil + @uri.to_s.should == "http://example.com/" + end + + it "should have a request URI of '/'" do + @uri.request_uri.should == "/" + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have the same hash as its duplicate" do + @uri.hash.should == @uri.dup.hash + end + + it "should have a different hash from its equivalent String value" do + @uri.hash.should_not == @uri.to_s.hash + end + + it "should have the same hash as an equal URI" do + @uri.hash.should == Addressable::URI.parse("http://example.com/").hash + end + + it "should be equivalent to http://EXAMPLE.com" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.com") + end + + it "should be equivalent to http://EXAMPLE.com:80/" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.com:80/") + end + + it "should have the same hash as http://example.com/" do + @uri.hash.should == Addressable::URI.parse("http://example.com/").hash + end + + it "should have the same hash as http://example.com after assignment" do + @uri.path = "" + @uri.hash.should == Addressable::URI.parse("http://example.com").hash + end + + it "should have the same hash as http://example.com/? after assignment" do + @uri.query = "" + @uri.hash.should == Addressable::URI.parse("http://example.com/?").hash + end + + it "should have the same hash as http://example.com/? after assignment" do + @uri.query_values = {} + @uri.hash.should == Addressable::URI.parse("http://example.com/?").hash + end + + it "should have the same hash as http://example.com/# after assignment" do + @uri.fragment = "" + @uri.hash.should == Addressable::URI.parse("http://example.com/#").hash + end + + it "should have a different hash from http://example.com" do + @uri.hash.should_not == Addressable::URI.parse("http://example.com").hash + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com?#'" do + before do + @uri = Addressable::URI.parse("http://example.com?#") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "", + :query => "", + :fragment => "" + } + end + + it "should have a request URI of '/?'" do + @uri.request_uri.should == "/?" + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == "http://example.com" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://@example.com/'" do + before do + @uri = Addressable::URI.parse("http://@example.com/") + end + + it "should be equivalent to http://example.com" do + @uri.should == Addressable::URI.parse("http://example.com") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => "", + :password => nil, + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com./'" do + before do + @uri = Addressable::URI.parse("http://example.com./") + end + + it "should be equivalent to http://example.com" do + @uri.should == Addressable::URI.parse("http://example.com") + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:@example.com/'" do + before do + @uri = Addressable::URI.parse("http://:@example.com/") + end + + it "should be equivalent to http://example.com" do + @uri.should == Addressable::URI.parse("http://example.com") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => "", + :password => "", + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/~smith/'" do + before do + @uri = Addressable::URI.parse("http://example.com/~smith/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com/%7Esmith/" do + @uri.should == Addressable::URI.parse("http://example.com/%7Esmith/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com/%7esmith/" do + @uri.should == Addressable::URI.parse("http://example.com/%7esmith/") + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%E8'" do + before do + @uri = Addressable::URI.parse("http://example.com/%E8") + end + + it "should not raise an exception when normalized" do + (lambda do + @uri.normalize + end).should_not raise_error + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com/%E8" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com/%E8" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path%2Fsegment/'" do + before do + @uri = Addressable::URI.parse("http://example.com/path%2Fsegment/") + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should be equal to 'http://example.com/path%2Fsegment/'" do + @uri.normalize.should be_eql( + Addressable::URI.parse("http://example.com/path%2Fsegment/") + ) + end + + it "should not be equal to 'http://example.com/path/segment/'" do + @uri.should_not == + Addressable::URI.parse("http://example.com/path/segment/") + end + + it "should not be equal to 'http://example.com/path/segment/'" do + @uri.normalize.should_not be_eql( + Addressable::URI.parse("http://example.com/path/segment/") + ) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?%F6'" do + before do + @uri = Addressable::URI.parse("http://example.com/?%F6") + end + + it "should not raise an exception when normalized" do + (lambda do + @uri.normalize + end).should_not raise_error + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com/?%F6" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com/?%F6" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/#%F6'" do + before do + @uri = Addressable::URI.parse("http://example.com/#%F6") + end + + it "should not raise an exception when normalized" do + (lambda do + @uri.normalize + end).should_not raise_error + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com/#%F6" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com/#%F6" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%C3%87'" do + before do + @uri = Addressable::URI.parse("http://example.com/%C3%87") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to 'http://example.com/C%CC%A7'" do + @uri.should == Addressable::URI.parse("http://example.com/C%CC%A7") + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com/%C3%87" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com/%C3%87" + end + + it "should raise an error if encoding with an unexpected return type" do + (lambda do + Addressable::URI.normalized_encode(@uri, Integer) + end).should raise_error(TypeError) + end + + it "if percent encoded should be 'http://example.com/C%25CC%25A7'" do + Addressable::URI.encode(@uri).to_s.should == + "http://example.com/%25C3%2587" + end + + it "if percent encoded should be 'http://example.com/C%25CC%25A7'" do + Addressable::URI.encode(@uri, Addressable::URI).should == + Addressable::URI.parse("http://example.com/%25C3%2587") + end + + it "should raise an error if encoding with an unexpected return type" do + (lambda do + Addressable::URI.encode(@uri, Integer) + end).should raise_error(TypeError) + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=string'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=string") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/'" do + @uri.path.should == "/" + end + + it "should have a query string of 'q=string'" do + @uri.query.should == "q=string" + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should not be considered relative" do + @uri.should_not be_relative + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:80/'" do + before do + @uri = Addressable::URI.parse("http://example.com:80/") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com:80'" do + @uri.authority.should == "example.com:80" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have explicit port 80" do + @uri.port.should == 80 + end + + it "should have a path of '/'" do + @uri.path.should == "/" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should not be considered relative" do + @uri.should_not be_relative + end + + it "should be exactly equal to http://example.com:80/" do + @uri.eql?(Addressable::URI.parse("http://example.com:80/")).should == true + end + + it "should be roughly equal to http://example.com/" do + (@uri === Addressable::URI.parse("http://example.com/")).should == true + end + + it "should be roughly equal to the string 'http://example.com/'" do + (@uri === "http://example.com/").should == true + end + + it "should not be roughly equal to the string " + + "'http://example.com:bogus/'" do + (lambda do + (@uri === "http://example.com:bogus/").should == false + end).should_not raise_error + end + + it "should result in itself when joined with itself" do + @uri.join(@uri).to_s.should == "http://example.com:80/" + @uri.join!(@uri).to_s.should == "http://example.com:80/" + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com/" do + @uri.should == Addressable::URI.parse("http://example.com/") + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com:/" do + @uri.should == Addressable::URI.parse("http://example.com:/") + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com:80/" do + @uri.should == Addressable::URI.parse("http://example.com:80/") + end + + # Section 6.2.2.1 of RFC 3986 + it "should be equal to http://EXAMPLE.COM/" do + @uri.should == Addressable::URI.parse("http://EXAMPLE.COM/") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => 80, + :path => "/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com:80/" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com:80/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:8080/'" do + before do + @uri = Addressable::URI.parse("http://example.com:8080/") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com:8080'" do + @uri.authority.should == "example.com:8080" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 8080" do + @uri.inferred_port.should == 8080 + end + + it "should have explicit port 8080" do + @uri.port.should == 8080 + end + + it "should have default port 80" do + @uri.default_port.should == 80 + end + + it "should have a path of '/'" do + @uri.path.should == "/" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should not be considered relative" do + @uri.should_not be_relative + end + + it "should be exactly equal to http://example.com:8080/" do + @uri.eql?(Addressable::URI.parse( + "http://example.com:8080/")).should == true + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://example.com/path/to/'" do + @uri.route_from("http://example.com/path/to/").should == + Addressable::URI.parse("http://example.com:8080/") + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://example.com:80/path/to/'" do + @uri.route_from("http://example.com:80/path/to/").should == + Addressable::URI.parse("http://example.com:8080/") + end + + it "should have a route of '../../' from " + + "'http://example.com:8080/path/to/'" do + @uri.route_from("http://example.com:8080/path/to/").should == + Addressable::URI.parse("../../") + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://user:pass@example.com/path/to/'" do + @uri.route_from("http://user:pass@example.com/path/to/").should == + Addressable::URI.parse("http://example.com:8080/") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => 8080, + :path => "/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com:8080'" do + @uri.origin.should == 'http://example.com:8080' + end + + it "should not change if encoded with the normalizing algorithm" do + Addressable::URI.normalized_encode(@uri).to_s.should == + "http://example.com:8080/" + Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s.should === + "http://example.com:8080/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:%38%30/'" do + before do + @uri = Addressable::URI.parse("http://example.com:%38%30/") + end + + it "should have the correct port" do + @uri.port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%2E/'" do + before do + @uri = Addressable::URI.parse("http://example.com/%2E/") + end + + it "should be considered to be in normal form" do + pending( + 'path segment normalization should happen before ' + + 'percent escaping normalization' + ) do + @uri.normalize.should be_eql(@uri) + end + end + + it "should normalize to 'http://example.com/%2E/'" do + pending( + 'path segment normalization should happen before ' + + 'percent escaping normalization' + ) do + @uri.normalize.should == "http://example.com/%2E/" + end + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/..") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/../..'" do + before do + @uri = Addressable::URI.parse("http://example.com/../..") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path(/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/path(/..") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/(path)/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/(path)/..") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path(/../'" do + before do + @uri = Addressable::URI.parse("http://example.com/path(/../") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/(path)/../'" do + before do + @uri = Addressable::URI.parse("http://example.com/(path)/../") + end + + it "should have the correct port" do + @uri.inferred_port.should == 80 + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when parsed from '/a/b/c/./../../g'" do + before do + @uri = Addressable::URI.parse("/a/b/c/./../../g") + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + # Section 5.2.4 of RFC 3986 + it "should normalize to '/a/g'" do + @uri.normalize.to_s.should == "/a/g" + end +end + +describe Addressable::URI, "when parsed from 'mid/content=5/../6'" do + before do + @uri = Addressable::URI.parse("mid/content=5/../6") + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + # Section 5.2.4 of RFC 3986 + it "should normalize to 'mid/6'" do + @uri.normalize.to_s.should == "mid/6" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.example.com///../'" do + before do + @uri = Addressable::URI.parse('http://www.example.com///../') + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end + + it "should normalize to 'http://www.example.com//'" do + @uri.normalize.to_s.should == "http://www.example.com//" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path/to/resource/'" do + before do + @uri = Addressable::URI.parse("http://example.com/path/to/resource/") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/path/to/resource/'" do + @uri.path.should == "/path/to/resource/" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should not be considered relative" do + @uri.should_not be_relative + end + + it "should be exactly equal to http://example.com:8080/" do + @uri.eql?(Addressable::URI.parse( + "http://example.com/path/to/resource/")).should == true + end + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/'" do + @uri.route_from("http://example.com/path/to/").should == + Addressable::URI.parse("resource/") + end + + it "should have a route of '../' from " + + "'http://example.com/path/to/resource/sub'" do + @uri.route_from("http://example.com/path/to/resource/sub").should == + Addressable::URI.parse("../") + end + + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/another'" do + @uri.route_from("http://example.com/path/to/another").should == + Addressable::URI.parse("resource/") + end + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/res'" do + @uri.route_from("http://example.com/path/to/res").should == + Addressable::URI.parse("resource/") + end + + it "should have a route of 'resource/' from " + + "'http://example.com:80/path/to/'" do + @uri.route_from("http://example.com:80/path/to/").should == + Addressable::URI.parse("resource/") + end + + it "should have a route of 'http://example.com/path/to/' from " + + "'http://example.com:8080/path/to/'" do + @uri.route_from("http://example.com:8080/path/to/").should == + Addressable::URI.parse("http://example.com/path/to/resource/") + end + + it "should have a route of 'http://example.com/path/to/' from " + + "'http://user:pass@example.com/path/to/'" do + @uri.route_from("http://user:pass@example.com/path/to/").should == + Addressable::URI.parse("http://example.com/path/to/resource/") + end + + it "should have a route of '../../path/to/resource/' from " + + "'http://example.com/to/resource/'" do + @uri.route_from("http://example.com/to/resource/").should == + Addressable::URI.parse("../../path/to/resource/") + end + + it "should correctly convert to a hash" do + @uri.to_hash.should == { + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "/path/to/resource/", + :query => nil, + :fragment => nil + } + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end +end + +describe Addressable::URI, "when parsed from " + + "'relative/path/to/resource'" do + before do + @uri = Addressable::URI.parse("relative/path/to/resource") + end + + it "should not have a scheme" do + @uri.scheme.should == nil + end + + it "should not be considered ip-based" do + @uri.should_not be_ip_based + end + + it "should not have an authority segment" do + @uri.authority.should == nil + end + + it "should not have a host" do + @uri.host.should == nil + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should not have a port" do + @uri.port.should == nil + end + + it "should have a path of 'relative/path/to/resource'" do + @uri.path.should == "relative/path/to/resource" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should not be considered absolute" do + @uri.should_not be_absolute + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "should raise an error if routing is attempted" do + (lambda do + @uri.route_to("http://example.com/") + end).should raise_error(ArgumentError, /relative\/path\/to\/resource/) + (lambda do + @uri.route_from("http://example.com/") + end).should raise_error(ArgumentError, /relative\/path\/to\/resource/) + end + + it "when joined with 'another/relative/path' should be " + + "'relative/path/to/another/relative/path'" do + @uri.join('another/relative/path').should == + Addressable::URI.parse("relative/path/to/another/relative/path") + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end +end + +describe Addressable::URI, "when parsed from " + + "'relative_path_with_no_slashes'" do + before do + @uri = Addressable::URI.parse("relative_path_with_no_slashes") + end + + it "should not have a scheme" do + @uri.scheme.should == nil + end + + it "should not be considered ip-based" do + @uri.should_not be_ip_based + end + + it "should not have an authority segment" do + @uri.authority.should == nil + end + + it "should not have a host" do + @uri.host.should == nil + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should not have a port" do + @uri.port.should == nil + end + + it "should have a path of 'relative_path_with_no_slashes'" do + @uri.path.should == "relative_path_with_no_slashes" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should not be considered absolute" do + @uri.should_not be_absolute + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "when joined with 'another_relative_path' should be " + + "'another_relative_path'" do + @uri.join('another_relative_path').should == + Addressable::URI.parse("another_relative_path") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt") + end + + it "should have a scheme of 'http'" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/file.txt'" do + @uri.path.should == "/file.txt" + end + + it "should have a basename of 'file.txt'" do + @uri.basename.should == "file.txt" + end + + it "should have an extname of '.txt'" do + @uri.extname.should == ".txt" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt;parameter'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt;parameter") + end + + it "should have a scheme of 'http'" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/file.txt;parameter'" do + @uri.path.should == "/file.txt;parameter" + end + + it "should have a basename of 'file.txt'" do + @uri.basename.should == "file.txt" + end + + it "should have an extname of '.txt'" do + @uri.extname.should == ".txt" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt;x=y'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt;x=y") + end + + it "should have a scheme of 'http'" do + @uri.scheme.should == "http" + end + + it "should have a scheme of 'http'" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'example.com'" do + @uri.authority.should == "example.com" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have no username" do + @uri.user.should == nil + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/file.txt;x=y'" do + @uri.path.should == "/file.txt;x=y" + end + + it "should have an extname of '.txt'" do + @uri.extname.should == ".txt" + end + + it "should have no query string" do + @uri.query.should == nil + end + + it "should have no fragment" do + @uri.fragment.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'svn+ssh://developername@rubyforge.org/var/svn/project'" do + before do + @uri = Addressable::URI.parse( + "svn+ssh://developername@rubyforge.org/var/svn/project" + ) + end + + it "should have a scheme of 'svn+ssh'" do + @uri.scheme.should == "svn+ssh" + end + + it "should be considered to be ip-based" do + @uri.should be_ip_based + end + + it "should have a path of '/var/svn/project'" do + @uri.path.should == "/var/svn/project" + end + + it "should have a username of 'developername'" do + @uri.user.should == "developername" + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'ssh+svn://developername@RUBYFORGE.ORG/var/svn/project'" do + before do + @uri = Addressable::URI.parse( + "ssh+svn://developername@RUBYFORGE.ORG/var/svn/project" + ) + end + + it "should have a scheme of 'ssh+svn'" do + @uri.scheme.should == "ssh+svn" + end + + it "should have a normalized scheme of 'svn+ssh'" do + @uri.normalized_scheme.should == "svn+ssh" + end + + it "should have a normalized site of 'svn+ssh'" do + @uri.normalized_site.should == "svn+ssh://developername@rubyforge.org" + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of '/var/svn/project'" do + @uri.path.should == "/var/svn/project" + end + + it "should have a username of 'developername'" do + @uri.user.should == "developername" + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should not be considered to be in normal form" do + @uri.normalize.should_not be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'mailto:user@example.com'" do + before do + @uri = Addressable::URI.parse("mailto:user@example.com") + end + + it "should have a scheme of 'mailto'" do + @uri.scheme.should == "mailto" + end + + it "should not be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of 'user@example.com'" do + @uri.path.should == "user@example.com" + end + + it "should have no user" do + @uri.user.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'tag:example.com,2006-08-18:/path/to/something'" do + before do + @uri = Addressable::URI.parse( + "tag:example.com,2006-08-18:/path/to/something") + end + + it "should have a scheme of 'tag'" do + @uri.scheme.should == "tag" + end + + it "should be considered to be ip-based" do + @uri.should_not be_ip_based + end + + it "should have a path of " + + "'example.com,2006-08-18:/path/to/something'" do + @uri.path.should == "example.com,2006-08-18:/path/to/something" + end + + it "should have no user" do + @uri.user.should == nil + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/x;y/'" do + before do + @uri = Addressable::URI.parse("http://example.com/x;y/") + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?x=1&y=2'" do + before do + @uri = Addressable::URI.parse("http://example.com/?x=1&y=2") + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'view-source:http://example.com/'" do + before do + @uri = Addressable::URI.parse("view-source:http://example.com/") + end + + it "should have a scheme of 'view-source'" do + @uri.scheme.should == "view-source" + end + + it "should have a path of 'http://example.com/'" do + @uri.path.should == "http://example.com/" + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment'" do + before do + @uri = Addressable::URI.parse( + "http://user:pass@example.com/path/to/resource?query=x#fragment") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have an authority segment of 'user:pass@example.com'" do + @uri.authority.should == "user:pass@example.com" + end + + it "should have a username of 'user'" do + @uri.user.should == "user" + end + + it "should have a password of 'pass'" do + @uri.password.should == "pass" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/path/to/resource'" do + @uri.path.should == "/path/to/resource" + end + + it "should have a query string of 'query=x'" do + @uri.query.should == "query=x" + end + + it "should have a fragment of 'fragment'" do + @uri.fragment.should == "fragment" + end + + it "should be considered to be in normal form" do + @uri.normalize.should be_eql(@uri) + end + + it "should have a route of '../../' to " + + "'http://user:pass@example.com/path/'" do + @uri.route_to("http://user:pass@example.com/path/").should == + Addressable::URI.parse("../../") + end + + it "should have a route of 'to/resource?query=x#fragment' " + + "from 'http://user:pass@example.com/path/'" do + @uri.route_from("http://user:pass@example.com/path/").should == + Addressable::URI.parse("to/resource?query=x#fragment") + end + + it "should have a route of '?query=x#fragment' " + + "from 'http://user:pass@example.com/path/to/resource'" do + @uri.route_from("http://user:pass@example.com/path/to/resource").should == + Addressable::URI.parse("?query=x#fragment") + end + + it "should have a route of '#fragment' " + + "from 'http://user:pass@example.com/path/to/resource?query=x'" do + @uri.route_from( + "http://user:pass@example.com/path/to/resource?query=x").should == + Addressable::URI.parse("#fragment") + end + + it "should have a route of '#fragment' from " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment'" do + @uri.route_from( + "http://user:pass@example.com/path/to/resource?query=x#fragment" + ).should == Addressable::URI.parse("#fragment") + end + + it "should have a route of 'http://elsewhere.com/' to " + + "'http://elsewhere.com/'" do + @uri.route_to("http://elsewhere.com/").should == + Addressable::URI.parse("http://elsewhere.com/") + end + + it "should have a route of " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment' " + + "from 'http://example.com/path/to/'" do + @uri.route_from("http://elsewhere.com/path/to/").should == + Addressable::URI.parse( + "http://user:pass@example.com/path/to/resource?query=x#fragment") + end + + it "should have the correct scheme after assignment" do + @uri.scheme = "ftp" + @uri.scheme.should == "ftp" + @uri.to_s.should == + "ftp://user:pass@example.com/path/to/resource?query=x#fragment" + @uri.to_str.should == + "ftp://user:pass@example.com/path/to/resource?query=x#fragment" + @uri.scheme = "bogus!" + @uri.scheme.should == "bogus!" + @uri.normalized_scheme.should == "bogus%21" + @uri.normalize.to_s.should == + "bogus%21://user:pass@example.com/path/to/resource?query=x#fragment" + @uri.normalize.to_str.should == + "bogus%21://user:pass@example.com/path/to/resource?query=x#fragment" + end + + it "should have the correct site segment after assignment" do + @uri.site = "https://newuser:newpass@example.com:443" + @uri.scheme.should == "https" + @uri.authority.should == "newuser:newpass@example.com:443" + @uri.user.should == "newuser" + @uri.password.should == "newpass" + @uri.userinfo.should == "newuser:newpass" + @uri.normalized_userinfo.should == "newuser:newpass" + @uri.host.should == "example.com" + @uri.port.should == 443 + @uri.inferred_port.should == 443 + @uri.to_s.should == + "https://newuser:newpass@example.com:443" + + "/path/to/resource?query=x#fragment" + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser:newpass@example.com:80" + @uri.authority.should == "newuser:newpass@example.com:80" + @uri.user.should == "newuser" + @uri.password.should == "newpass" + @uri.userinfo.should == "newuser:newpass" + @uri.normalized_userinfo.should == "newuser:newpass" + @uri.host.should == "example.com" + @uri.port.should == 80 + @uri.inferred_port.should == 80 + @uri.to_s.should == + "http://newuser:newpass@example.com:80" + + "/path/to/resource?query=x#fragment" + end + + it "should have the correct userinfo segment after assignment" do + @uri.userinfo = "newuser:newpass" + @uri.userinfo.should == "newuser:newpass" + @uri.authority.should == "newuser:newpass@example.com" + @uri.user.should == "newuser" + @uri.password.should == "newpass" + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == + "http://newuser:newpass@example.com" + + "/path/to/resource?query=x#fragment" + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.authority.should == "newuser:pass@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.authority.should == "user:newpass@example.com" + end + + it "should have the correct host after assignment" do + @uri.host = "newexample.com" + @uri.host.should == "newexample.com" + @uri.authority.should == "user:pass@newexample.com" + end + + it "should have the correct port after assignment" do + @uri.port = 8080 + @uri.port.should == 8080 + @uri.authority.should == "user:pass@example.com:8080" + end + + it "should have the correct path after assignment" do + @uri.path = "/newpath/to/resource" + @uri.path.should == "/newpath/to/resource" + @uri.to_s.should == + "http://user:pass@example.com/newpath/to/resource?query=x#fragment" + end + + it "should have the correct scheme and authority after nil assignment" do + @uri.site = nil + @uri.scheme.should == nil + @uri.authority.should == nil + @uri.to_s.should == "/path/to/resource?query=x#fragment" + end + + it "should have the correct scheme and authority after assignment" do + @uri.site = "file://" + @uri.scheme.should == "file" + @uri.authority.should == "" + @uri.to_s.should == "file:///path/to/resource?query=x#fragment" + end + + it "should have the correct path after nil assignment" do + @uri.path = nil + @uri.path.should == "" + @uri.to_s.should == + "http://user:pass@example.com?query=x#fragment" + end + + it "should have the correct query string after assignment" do + @uri.query = "newquery=x" + @uri.query.should == "newquery=x" + @uri.to_s.should == + "http://user:pass@example.com/path/to/resource?newquery=x#fragment" + @uri.query = nil + @uri.query.should == nil + @uri.to_s.should == + "http://user:pass@example.com/path/to/resource#fragment" + end + + it "should have the correct query string after hash assignment" do + @uri.query_values = {"?uestion mark" => "=sign", "hello" => "g\xC3\xBCnther"} + @uri.query.split("&").should include("%3Fuestion%20mark=%3Dsign") + @uri.query.split("&").should include("hello=g%C3%BCnther") + @uri.query_values.should == { + "?uestion mark" => "=sign", "hello" => "g\xC3\xBCnther" + } + end + + it "should have the correct query string after flag hash assignment" do + @uri.query_values = {'flag?1' => nil, 'fl=ag2' => nil, 'flag3' => nil} + @uri.query.split("&").should include("flag%3F1") + @uri.query.split("&").should include("fl%3Dag2") + @uri.query.split("&").should include("flag3") + @uri.query_values(Array).sort.should == [["fl=ag2"], ["flag3"], ["flag?1"]] + @uri.query_values(Hash).should == { + 'flag?1' => nil, 'fl=ag2' => nil, 'flag3' => nil + } + end + + it "should raise an error if query values are set to a bogus type" do + (lambda do + @uri.query_values = "bogus" + end).should raise_error(TypeError) + end + + it "should have the correct fragment after assignment" do + @uri.fragment = "newfragment" + @uri.fragment.should == "newfragment" + @uri.to_s.should == + "http://user:pass@example.com/path/to/resource?query=x#newfragment" + + @uri.fragment = nil + @uri.fragment.should == nil + @uri.to_s.should == + "http://user:pass@example.com/path/to/resource?query=x" + end + + it "should have the correct values after a merge" do + @uri.merge(:fragment => "newfragment").to_s.should == + "http://user:pass@example.com/path/to/resource?query=x#newfragment" + end + + it "should have the correct values after a merge" do + @uri.merge(:fragment => nil).to_s.should == + "http://user:pass@example.com/path/to/resource?query=x" + end + + it "should have the correct values after a merge" do + @uri.merge(:userinfo => "newuser:newpass").to_s.should == + "http://newuser:newpass@example.com/path/to/resource?query=x#fragment" + end + + it "should have the correct values after a merge" do + @uri.merge(:userinfo => nil).to_s.should == + "http://example.com/path/to/resource?query=x#fragment" + end + + it "should have the correct values after a merge" do + @uri.merge(:path => "newpath").to_s.should == + "http://user:pass@example.com/newpath?query=x#fragment" + end + + it "should have the correct values after a merge" do + @uri.merge(:port => "42", :path => "newpath", :query => "").to_s.should == + "http://user:pass@example.com:42/newpath?#fragment" + end + + it "should have the correct values after a merge" do + @uri.merge(:authority => "foo:bar@baz:42").to_s.should == + "http://foo:bar@baz:42/path/to/resource?query=x#fragment" + # Ensure the operation was not destructive + @uri.to_s.should == + "http://user:pass@example.com/path/to/resource?query=x#fragment" + end + + it "should have the correct values after a destructive merge" do + @uri.merge!(:authority => "foo:bar@baz:42") + # Ensure the operation was destructive + @uri.to_s.should == + "http://foo:bar@baz:42/path/to/resource?query=x#fragment" + end + + it "should fail to merge with bogus values" do + (lambda do + @uri.merge(:port => "bogus") + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should fail to merge with bogus values" do + (lambda do + @uri.merge(:authority => "bar@baz:bogus") + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should fail to merge with bogus parameters" do + (lambda do + @uri.merge(42) + end).should raise_error(TypeError) + end + + it "should fail to merge with bogus parameters" do + (lambda do + @uri.merge("http://example.com/") + end).should raise_error(TypeError) + end + + it "should fail to merge with both authority and subcomponents" do + (lambda do + @uri.merge(:authority => "foo:bar@baz:42", :port => "42") + end).should raise_error(ArgumentError) + end + + it "should fail to merge with both userinfo and subcomponents" do + (lambda do + @uri.merge(:userinfo => "foo:bar", :user => "foo") + end).should raise_error(ArgumentError) + end + + it "should be identical to its duplicate" do + @uri.should == @uri.dup + end + + it "should have an origin of 'http://example.com'" do + @uri.origin.should == 'http://example.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/search?q=Q%26A'" do + + before do + @uri = Addressable::URI.parse("http://example.com/search?q=Q%26A") + end + + it "should have a query of 'q=Q%26A'" do + @uri.query.should == "q=Q%26A" + end + + it "should have query_values of {'q' => 'Q&A'}" do + @uri.query_values.should == { 'q' => 'Q&A' } + end + + it "should normalize to the original uri " + + "(with the ampersand properly percent-encoded)" do + @uri.normalize.to_s.should == "http://example.com/search?q=Q%26A" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?&x=b") + end + + it "should have a query of '&x=b'" do + @uri.query.should == "&x=b" + end + + it "should have query_values of {'x' => 'b'}" do + @uri.query_values.should == {'x' => 'b'} + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q='one;two'&x=1'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q='one;two'&x=1") + end + + it "should have a query of 'q='one;two'&x=1'" do + @uri.query.should == "q='one;two'&x=1" + end + + it "should have query_values of {\"q\" => \"'one;two'\", \"x\" => \"1\"}" do + @uri.query_values.should == {"q" => "'one;two'", "x" => "1"} + end + + it "should escape the ';' character when normalizing to avoid ambiguity " + + "with the W3C HTML 4.01 specification" do + # HTML 4.01 Section B.2.2 + @uri.normalize.query.should == "q='one%3Btwo'&x=1" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?&&x=b") + end + + it "should have a query of '&&x=b'" do + @uri.query.should == "&&x=b" + end + + it "should have query_values of {'x' => 'b'}" do + @uri.query_values.should == {'x' => 'b'} + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a&&x=b") + end + + it "should have a query of 'q=a&&x=b'" do + @uri.query.should == "q=a&&x=b" + end + + it "should have query_values of {'q' => 'a, 'x' => 'b'}" do + @uri.query_values.should == {'q' => 'a', 'x' => 'b'} + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q&&x=b") + end + + it "should have a query of 'q&&x=b'" do + @uri.query.should == "q&&x=b" + end + + it "should have query_values of {'q' => true, 'x' => 'b'}" do + @uri.query_values.should == {'q' => nil, 'x' => 'b'} + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a+b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a+b") + end + + it "should have a query of 'q=a+b'" do + @uri.query.should == "q=a+b" + end + + it "should have query_values of {'q' => 'a b'}" do + @uri.query_values.should == {'q' => 'a b'} + end + + it "should have a normalized query of 'q=a+b'" do + @uri.normalized_query.should == "q=a+b" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a%2bb'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a%2bb") + end + + it "should have a query of 'q=a+b'" do + @uri.query.should == "q=a%2bb" + end + + it "should have query_values of {'q' => 'a+b'}" do + @uri.query_values.should == {'q' => 'a+b'} + end + + it "should have a normalized query of 'q=a%2Bb'" do + @uri.normalized_query.should == "q=a%2Bb" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?v=%7E&w=%&x=%25&y=%2B&z=C%CC%A7'" do + before do + @uri = Addressable::URI.parse("http://example.com/?v=%7E&w=%&x=%25&y=%2B&z=C%CC%A7") + end + + it "should have a normalized query of 'v=~&w=%25&x=%25&y=%2B&z=%C3%87'" do + @uri.normalized_query.should == "v=~&w=%25&x=%25&y=%2B&z=%C3%87" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?v=%7E&w=%&x=%25&y=+&z=C%CC%A7'" do + before do + @uri = Addressable::URI.parse("http://example.com/?v=%7E&w=%&x=%25&y=+&z=C%CC%A7") + end + + it "should have a normalized query of 'v=~&w=%25&x=%25&y=+&z=%C3%87'" do + @uri.normalized_query.should == "v=~&w=%25&x=%25&y=+&z=%C3%87" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/sound%2bvision'" do + before do + @uri = Addressable::URI.parse("http://example.com/sound%2bvision") + end + + it "should have a normalized path of '/sound+vision'" do + @uri.normalized_path.should == '/sound+vision' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q='" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=") + end + + it "should have a query of 'q='" do + @uri.query.should == "q=" + end + + it "should have query_values of {'q' => ''}" do + @uri.query_values.should == {'q' => ''} + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user@example.com'" do + before do + @uri = Addressable::URI.parse("http://user@example.com") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a username of 'user'" do + @uri.user.should == "user" + end + + it "should have no password" do + @uri.password.should == nil + end + + it "should have a userinfo of 'user'" do + @uri.userinfo.should == "user" + end + + it "should have a normalized userinfo of 'user'" do + @uri.normalized_userinfo.should == "user" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have default_port 80" do + @uri.default_port.should == 80 + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password.should == nil + @uri.to_s.should == "http://newuser@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.to_s.should == "http://user:newpass@example.com" + end + + it "should have the correct userinfo segment after assignment" do + @uri.userinfo = "newuser:newpass" + @uri.userinfo.should == "newuser:newpass" + @uri.user.should == "newuser" + @uri.password.should == "newpass" + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://newuser:newpass@example.com" + end + + it "should have the correct userinfo segment after nil assignment" do + @uri.userinfo = nil + @uri.userinfo.should == nil + @uri.user.should == nil + @uri.password.should == nil + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://example.com" + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser@example.com" + @uri.authority.should == "newuser@example.com" + @uri.user.should == "newuser" + @uri.password.should == nil + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://newuser@example.com" + end + + it "should raise an error after nil assignment of authority segment" do + (lambda do + # This would create an invalid URI + @uri.authority = nil + end).should raise_error + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user:@example.com'" do + before do + @uri = Addressable::URI.parse("http://user:@example.com") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a username of 'user'" do + @uri.user.should == "user" + end + + it "should have a password of ''" do + @uri.password.should == "" + end + + it "should have a normalized userinfo of 'user:'" do + @uri.normalized_userinfo.should == "user:" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password.should == "" + @uri.to_s.should == "http://newuser:@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.to_s.should == "http://user:newpass@example.com" + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser:@example.com" + @uri.authority.should == "newuser:@example.com" + @uri.user.should == "newuser" + @uri.password.should == "" + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://newuser:@example.com" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:pass@example.com'" do + before do + @uri = Addressable::URI.parse("http://:pass@example.com") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a username of ''" do + @uri.user.should == "" + end + + it "should have a password of 'pass'" do + @uri.password.should == "pass" + end + + it "should have a userinfo of ':pass'" do + @uri.userinfo.should == ":pass" + end + + it "should have a normalized userinfo of ':pass'" do + @uri.normalized_userinfo.should == ":pass" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password.should == "pass" + @uri.to_s.should == "http://newuser:pass@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.user.should == "" + @uri.to_s.should == "http://:newpass@example.com" + end + + it "should have the correct authority segment after assignment" do + @uri.authority = ":newpass@example.com" + @uri.authority.should == ":newpass@example.com" + @uri.user.should == "" + @uri.password.should == "newpass" + @uri.host.should == "example.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://:newpass@example.com" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:@example.com'" do + before do + @uri = Addressable::URI.parse("http://:@example.com") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a username of ''" do + @uri.user.should == "" + end + + it "should have a password of ''" do + @uri.password.should == "" + end + + it "should have a normalized userinfo of nil" do + @uri.normalized_userinfo.should == nil + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + @uri.user.should == "newuser" + @uri.password.should == "" + @uri.to_s.should == "http://newuser:@example.com" + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + @uri.password.should == "newpass" + @uri.user.should == "" + @uri.to_s.should == "http://:newpass@example.com" + end + + it "should have the correct authority segment after assignment" do + @uri.authority = ":@newexample.com" + @uri.authority.should == ":@newexample.com" + @uri.user.should == "" + @uri.password.should == "" + @uri.host.should == "newexample.com" + @uri.port.should == nil + @uri.inferred_port.should == 80 + @uri.to_s.should == "http://:@newexample.com" + end +end + +describe Addressable::URI, "when parsed from " + + "'#example'" do + before do + @uri = Addressable::URI.parse("#example") + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "should have a host of nil" do + @uri.host.should == nil + end + + it "should have a site of nil" do + @uri.site.should == nil + end + + it "should have a normalized_site of nil" do + @uri.normalized_site.should == nil + end + + it "should have a path of ''" do + @uri.path.should == "" + end + + it "should have a query string of nil" do + @uri.query.should == nil + end + + it "should have a fragment of 'example'" do + @uri.fragment.should == "example" + end +end + +describe Addressable::URI, "when parsed from " + + "the network-path reference '//example.com/'" do + before do + @uri = Addressable::URI.parse("//example.com/") + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should have a path of '/'" do + @uri.path.should == "/" + end + + it "should raise an error if routing is attempted" do + (lambda do + @uri.route_to("http://example.com/") + end).should raise_error(ArgumentError, /\/\/example.com\//) + (lambda do + @uri.route_from("http://example.com/") + end).should raise_error(ArgumentError, /\/\/example.com\//) + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from " + + "'feed://http://example.com/'" do + before do + @uri = Addressable::URI.parse("feed://http://example.com/") + end + + it "should have a host of 'http'" do + @uri.host.should == "http" + end + + it "should have a path of '//example.com/'" do + @uri.path.should == "//example.com/" + end +end + +describe Addressable::URI, "when parsed from " + + "'feed:http://example.com/'" do + before do + @uri = Addressable::URI.parse("feed:http://example.com/") + end + + it "should have a path of 'http://example.com/'" do + @uri.path.should == "http://example.com/" + end + + it "should normalize to 'http://example.com/'" do + @uri.normalize.to_s.should == "http://example.com/" + @uri.normalize!.to_s.should == "http://example.com/" + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from " + + "'example://a/b/c/%7Bfoo%7D'" do + before do + @uri = Addressable::URI.parse("example://a/b/c/%7Bfoo%7D") + end + + # Section 6.2.2 of RFC 3986 + it "should be equivalent to eXAMPLE://a/./b/../b/%63/%7bfoo%7d" do + @uri.should == + Addressable::URI.parse("eXAMPLE://a/./b/../b/%63/%7bfoo%7d") + end + + it "should have an origin of 'example://a'" do + @uri.origin.should == 'example://a' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/indirect/path/./to/../resource/'" do + before do + @uri = Addressable::URI.parse( + "http://example.com/indirect/path/./to/../resource/") + end + + it "should use the 'http' scheme" do + @uri.scheme.should == "http" + end + + it "should have a host of 'example.com'" do + @uri.host.should == "example.com" + end + + it "should use port 80" do + @uri.inferred_port.should == 80 + end + + it "should have a path of '/indirect/path/./to/../resource/'" do + @uri.path.should == "/indirect/path/./to/../resource/" + end + + # Section 6.2.2.3 of RFC 3986 + it "should have a normalized path of '/indirect/path/resource/'" do + @uri.normalize.path.should == "/indirect/path/resource/" + @uri.normalize!.path.should == "/indirect/path/resource/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://under_score.example.com/'" do + it "should not cause an error" do + (lambda do + Addressable::URI.parse("http://under_score.example.com/") + end).should_not raise_error + end +end + +describe Addressable::URI, "when parsed from " + + "'./this:that'" do + before do + @uri = Addressable::URI.parse("./this:that") + end + + it "should be considered relative" do + @uri.should be_relative + end + + it "should have no scheme" do + @uri.scheme.should == nil + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from " + + "'this:that'" do + before do + @uri = Addressable::URI.parse("this:that") + end + + it "should be considered absolute" do + @uri.should be_absolute + end + + it "should have a scheme of 'this'" do + @uri.scheme.should == "this" + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from '?'" do + before do + @uri = Addressable::URI.parse("?") + end + + it "should normalize to ''" do + @uri.normalize.to_s.should == "" + end + + it "should have the correct return type" do + @uri.query_values.should == {} + @uri.query_values(Hash).should == {} + @uri.query_values(Array).should == [] + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from '?one=1&two=2&three=3'" do + before do + @uri = Addressable::URI.parse("?one=1&two=2&three=3") + end + + it "should have the correct query values" do + @uri.query_values.should == {"one" => "1", "two" => "2", "three" => "3"} + end + + it "should raise an error for invalid return type values" do + (lambda do + @uri.query_values(Fixnum) + end).should raise_error(ArgumentError) + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one", "1"], ["two", "2"], ["three", "3"] + ] + end + + it "should have a 'null' origin" do + @uri.origin.should == 'null' + end +end + +describe Addressable::URI, "when parsed from '?one=1=uno&two=2=dos'" do + before do + @uri = Addressable::URI.parse("?one=1=uno&two=2=dos") + end + + it "should have the correct query values" do + @uri.query_values.should == {"one" => "1=uno", "two" => "2=dos"} + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one", "1=uno"], ["two", "2=dos"] + ] + end +end + +describe Addressable::URI, "when parsed from '?one[two][three]=four'" do + before do + @uri = Addressable::URI.parse("?one[two][three]=four") + end + + it "should have the correct query values" do + @uri.query_values.should == {"one[two][three]" => "four"} + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one[two][three]", "four"] + ] + end +end + +describe Addressable::URI, "when parsed from '?one.two.three=four'" do + before do + @uri = Addressable::URI.parse("?one.two.three=four") + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one.two.three" => "four" + } + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one.two.three", "four"] + ] + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three]=four&one[two][five]=six'" do + before do + @uri = Addressable::URI.parse("?one[two][three]=four&one[two][five]=six") + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one[two][three]" => "four", "one[two][five]" => "six" + } + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one[two][three]", "four"], ["one[two][five]", "six"] + ] + end +end + +describe Addressable::URI, "when parsed from " + + "'?one.two.three=four&one.two.five=six'" do + before do + @uri = Addressable::URI.parse("?one.two.three=four&one.two.five=six") + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one.two.three" => "four", "one.two.five" => "six" + } + end + + it "should have the correct array query values" do + @uri.query_values(Array).should == [ + ["one.two.three", "four"], ["one.two.five", "six"] + ] + end +end + +describe Addressable::URI, "when parsed from " + + "'?one=two&one=three'" do + before do + @uri = Addressable::URI.parse( + "?one=two&one=three&one=four" + ) + end + + it "should have correct array query values" do + @uri.query_values(Array).should == + [['one', 'two'], ['one', 'three'], ['one', 'four']] + end + + it "should have correct hash query values" do + pending("This is probably more desirable behavior.") do + @uri.query_values(Hash).should == + {'one' => ['two', 'three', 'four']} + end + end + + it "should handle assignment with keys of mixed type" do + @uri.query_values = @uri.query_values(Hash).merge({:one => 'three'}) + @uri.query_values(Hash).should == {'one' => 'three'} + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][]=four&one[two][three][]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][]=four&one[two][three][]=five" + ) + end + + it "should have correct query values" do + @uri.query_values(Hash).should == {"one[two][three][]" => "five"} + end + + it "should have correct array query values" do + @uri.query_values(Array).should == [ + ["one[two][three][]", "four"], ["one[two][three][]", "five"] + ] + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][0]=four&one[two][three][1]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][0]=four&one[two][three][1]=five" + ) + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one[two][three][0]" => "four", "one[two][three][1]" => "five" + } + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][1]=four&one[two][three][0]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][1]=four&one[two][three][0]=five" + ) + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one[two][three][1]" => "four", "one[two][three][0]" => "five" + } + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][2]=four&one[two][three][1]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][2]=four&one[two][three][1]=five" + ) + end + + it "should have the correct query values" do + @uri.query_values.should == { + "one[two][three][2]" => "four", "one[two][three][1]" => "five" + } + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/") + end + + it "should be equivalent to 'http://www.xn--8ws00zhy3a.com/'" do + @uri.should == + Addressable::URI.parse("http://www.xn--8ws00zhy3a.com/") + end + + it "should not have domain name encoded during normalization" do + Addressable::URI.normalized_encode(@uri.to_s).should == + "http://www.詹姆斯.com/" + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + @uri.origin.should == 'http://www.xn--8ws00zhy3a.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/ some spaces /'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/ some spaces /") + end + + it "should be equivalent to " + + "'http://www.xn--8ws00zhy3a.com/%20some%20spaces%20/'" do + @uri.should == + Addressable::URI.parse( + "http://www.xn--8ws00zhy3a.com/%20some%20spaces%20/") + end + + it "should not have domain name encoded during normalization" do + Addressable::URI.normalized_encode(@uri.to_s).should == + "http://www.詹姆斯.com/%20some%20spaces%20/" + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + @uri.origin.should == 'http://www.xn--8ws00zhy3a.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.xn--8ws00zhy3a.com/'" do + before do + @uri = Addressable::URI.parse("http://www.xn--8ws00zhy3a.com/") + end + + it "should be displayed as http://www.詹姆斯.com/" do + @uri.display_uri.to_s.should == "http://www.詹姆斯.com/" + end + + it "should properly force the encoding" do + display_string = @uri.display_uri.to_str + display_string.should == "http://www.詹姆斯.com/" + if display_string.respond_to?(:encoding) + display_string.encoding.to_s.should == Encoding::UTF_8.to_s + end + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + @uri.origin.should == 'http://www.xn--8ws00zhy3a.com' + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/atomtests/iri/詹.html'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/atomtests/iri/詹.html") + end + + it "should normalize to " + + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" do + @uri.normalize.to_s.should == + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" + @uri.normalize!.to_s.should == + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" + end +end + +describe Addressable::URI, "when parsed from a percent-encoded IRI" do + before do + @uri = Addressable::URI.parse( + "http://www.%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA" + + "%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3" + + "%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82" + + "%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0" + + "%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3" + + "%81%9F%E3%82%8A%E3%81%AA%E3%81%84.w3.mag.keio.ac.jp" + ) + end + + it "should normalize to something sane" do + @uri.normalize.to_s.should == + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp/" + @uri.normalize!.to_s.should == + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp/" + end + + it "should have the correct origin" do + @uri.origin.should == ( + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + ) + end +end + +describe Addressable::URI, "with a base uri of 'http://a/b/c/d;p?q'" do + before do + @uri = Addressable::URI.parse("http://a/b/c/d;p?q") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g:h' should resolve to g:h" do + (@uri + "g:h").to_s.should == "g:h" + Addressable::URI.join(@uri, "g:h").to_s.should == "g:h" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g' should resolve to http://a/b/c/g" do + (@uri + "g").to_s.should == "http://a/b/c/g" + Addressable::URI.join(@uri.to_s, "g").to_s.should == "http://a/b/c/g" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with './g' should resolve to http://a/b/c/g" do + (@uri + "./g").to_s.should == "http://a/b/c/g" + Addressable::URI.join(@uri.to_s, "./g").to_s.should == "http://a/b/c/g" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g/' should resolve to http://a/b/c/g/" do + (@uri + "g/").to_s.should == "http://a/b/c/g/" + Addressable::URI.join(@uri.to_s, "g/").to_s.should == "http://a/b/c/g/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '/g' should resolve to http://a/g" do + (@uri + "/g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "/g").to_s.should == "http://a/g" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '//g' should resolve to http://g" do + (@uri + "//g").to_s.should == "http://g" + Addressable::URI.join(@uri.to_s, "//g").to_s.should == "http://g" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '?y' should resolve to http://a/b/c/d;p?y" do + (@uri + "?y").to_s.should == "http://a/b/c/d;p?y" + Addressable::URI.join(@uri.to_s, "?y").to_s.should == "http://a/b/c/d;p?y" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g?y' should resolve to http://a/b/c/g?y" do + (@uri + "g?y").to_s.should == "http://a/b/c/g?y" + Addressable::URI.join(@uri.to_s, "g?y").to_s.should == "http://a/b/c/g?y" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '#s' should resolve to http://a/b/c/d;p?q#s" do + (@uri + "#s").to_s.should == "http://a/b/c/d;p?q#s" + Addressable::URI.join(@uri.to_s, "#s").to_s.should == + "http://a/b/c/d;p?q#s" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g#s' should resolve to http://a/b/c/g#s" do + (@uri + "g#s").to_s.should == "http://a/b/c/g#s" + Addressable::URI.join(@uri.to_s, "g#s").to_s.should == "http://a/b/c/g#s" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g?y#s' should resolve to http://a/b/c/g?y#s" do + (@uri + "g?y#s").to_s.should == "http://a/b/c/g?y#s" + Addressable::URI.join( + @uri.to_s, "g?y#s").to_s.should == "http://a/b/c/g?y#s" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with ';x' should resolve to http://a/b/c/;x" do + (@uri + ";x").to_s.should == "http://a/b/c/;x" + Addressable::URI.join(@uri.to_s, ";x").to_s.should == "http://a/b/c/;x" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g;x' should resolve to http://a/b/c/g;x" do + (@uri + "g;x").to_s.should == "http://a/b/c/g;x" + Addressable::URI.join(@uri.to_s, "g;x").to_s.should == "http://a/b/c/g;x" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g;x?y#s' should resolve to http://a/b/c/g;x?y#s" do + (@uri + "g;x?y#s").to_s.should == "http://a/b/c/g;x?y#s" + Addressable::URI.join( + @uri.to_s, "g;x?y#s").to_s.should == "http://a/b/c/g;x?y#s" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '' should resolve to http://a/b/c/d;p?q" do + (@uri + "").to_s.should == "http://a/b/c/d;p?q" + Addressable::URI.join(@uri.to_s, "").to_s.should == "http://a/b/c/d;p?q" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '.' should resolve to http://a/b/c/" do + (@uri + ".").to_s.should == "http://a/b/c/" + Addressable::URI.join(@uri.to_s, ".").to_s.should == "http://a/b/c/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with './' should resolve to http://a/b/c/" do + (@uri + "./").to_s.should == "http://a/b/c/" + Addressable::URI.join(@uri.to_s, "./").to_s.should == "http://a/b/c/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '..' should resolve to http://a/b/" do + (@uri + "..").to_s.should == "http://a/b/" + Addressable::URI.join(@uri.to_s, "..").to_s.should == "http://a/b/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../' should resolve to http://a/b/" do + (@uri + "../").to_s.should == "http://a/b/" + Addressable::URI.join(@uri.to_s, "../").to_s.should == "http://a/b/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../g' should resolve to http://a/b/g" do + (@uri + "../g").to_s.should == "http://a/b/g" + Addressable::URI.join(@uri.to_s, "../g").to_s.should == "http://a/b/g" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../..' should resolve to http://a/" do + (@uri + "../..").to_s.should == "http://a/" + Addressable::URI.join(@uri.to_s, "../..").to_s.should == "http://a/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../../' should resolve to http://a/" do + (@uri + "../../").to_s.should == "http://a/" + Addressable::URI.join(@uri.to_s, "../../").to_s.should == "http://a/" + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../../g' should resolve to http://a/g" do + (@uri + "../../g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "../../g").to_s.should == "http://a/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '../../../g' should resolve to http://a/g" do + (@uri + "../../../g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "../../../g").to_s.should == "http://a/g" + end + + it "when joined with '../.././../g' should resolve to http://a/g" do + (@uri + "../.././../g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "../.././../g").to_s.should == + "http://a/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '../../../../g' should resolve to http://a/g" do + (@uri + "../../../../g").to_s.should == "http://a/g" + Addressable::URI.join( + @uri.to_s, "../../../../g").to_s.should == "http://a/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '/./g' should resolve to http://a/g" do + (@uri + "/./g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "/./g").to_s.should == "http://a/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '/../g' should resolve to http://a/g" do + (@uri + "/../g").to_s.should == "http://a/g" + Addressable::URI.join(@uri.to_s, "/../g").to_s.should == "http://a/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g.' should resolve to http://a/b/c/g." do + (@uri + "g.").to_s.should == "http://a/b/c/g." + Addressable::URI.join(@uri.to_s, "g.").to_s.should == "http://a/b/c/g." + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '.g' should resolve to http://a/b/c/.g" do + (@uri + ".g").to_s.should == "http://a/b/c/.g" + Addressable::URI.join(@uri.to_s, ".g").to_s.should == "http://a/b/c/.g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g..' should resolve to http://a/b/c/g.." do + (@uri + "g..").to_s.should == "http://a/b/c/g.." + Addressable::URI.join(@uri.to_s, "g..").to_s.should == "http://a/b/c/g.." + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '..g' should resolve to http://a/b/c/..g" do + (@uri + "..g").to_s.should == "http://a/b/c/..g" + Addressable::URI.join(@uri.to_s, "..g").to_s.should == "http://a/b/c/..g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with './../g' should resolve to http://a/b/g" do + (@uri + "./../g").to_s.should == "http://a/b/g" + Addressable::URI.join(@uri.to_s, "./../g").to_s.should == "http://a/b/g" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with './g/.' should resolve to http://a/b/c/g/" do + (@uri + "./g/.").to_s.should == "http://a/b/c/g/" + Addressable::URI.join(@uri.to_s, "./g/.").to_s.should == "http://a/b/c/g/" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g/./h' should resolve to http://a/b/c/g/h" do + (@uri + "g/./h").to_s.should == "http://a/b/c/g/h" + Addressable::URI.join(@uri.to_s, "g/./h").to_s.should == "http://a/b/c/g/h" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g/../h' should resolve to http://a/b/c/h" do + (@uri + "g/../h").to_s.should == "http://a/b/c/h" + Addressable::URI.join(@uri.to_s, "g/../h").to_s.should == "http://a/b/c/h" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g;x=1/./y' " + + "should resolve to http://a/b/c/g;x=1/y" do + (@uri + "g;x=1/./y").to_s.should == "http://a/b/c/g;x=1/y" + Addressable::URI.join( + @uri.to_s, "g;x=1/./y").to_s.should == "http://a/b/c/g;x=1/y" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g;x=1/../y' should resolve to http://a/b/c/y" do + (@uri + "g;x=1/../y").to_s.should == "http://a/b/c/y" + Addressable::URI.join( + @uri.to_s, "g;x=1/../y").to_s.should == "http://a/b/c/y" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g?y/./x' " + + "should resolve to http://a/b/c/g?y/./x" do + (@uri + "g?y/./x").to_s.should == "http://a/b/c/g?y/./x" + Addressable::URI.join( + @uri.to_s, "g?y/./x").to_s.should == "http://a/b/c/g?y/./x" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g?y/../x' " + + "should resolve to http://a/b/c/g?y/../x" do + (@uri + "g?y/../x").to_s.should == "http://a/b/c/g?y/../x" + Addressable::URI.join( + @uri.to_s, "g?y/../x").to_s.should == "http://a/b/c/g?y/../x" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g#s/./x' " + + "should resolve to http://a/b/c/g#s/./x" do + (@uri + "g#s/./x").to_s.should == "http://a/b/c/g#s/./x" + Addressable::URI.join( + @uri.to_s, "g#s/./x").to_s.should == "http://a/b/c/g#s/./x" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g#s/../x' " + + "should resolve to http://a/b/c/g#s/../x" do + (@uri + "g#s/../x").to_s.should == "http://a/b/c/g#s/../x" + Addressable::URI.join( + @uri.to_s, "g#s/../x").to_s.should == "http://a/b/c/g#s/../x" + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'http:g' should resolve to http:g" do + (@uri + "http:g").to_s.should == "http:g" + Addressable::URI.join(@uri.to_s, "http:g").to_s.should == "http:g" + end + + # Edge case to be sure + it "when joined with '//example.com/' should " + + "resolve to http://example.com/" do + (@uri + "//example.com/").to_s.should == "http://example.com/" + Addressable::URI.join( + @uri.to_s, "//example.com/").to_s.should == "http://example.com/" + end + + it "when joined with a bogus object a TypeError should be raised" do + (lambda do + Addressable::URI.join(@uri, 42) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when converting the path " + + "'relative/path/to/something'" do + before do + @path = 'relative/path/to/something' + end + + it "should convert to " + + "\'relative/path/to/something\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "relative/path/to/something" + end + + it "should join with an absolute file path correctly" do + @base = Addressable::URI.convert_path("/absolute/path/") + @uri = Addressable::URI.convert_path(@path) + (@base + @uri).to_str.should == + "file:///absolute/path/relative/path/to/something" + end +end + +describe Addressable::URI, "when converting a bogus path" do + it "should raise a TypeError" do + (lambda do + Addressable::URI.convert_path(42) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when given a UNIX root directory" do + before do + @path = "/" + end + + it "should convert to \'file:///\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given a Windows root directory" do + before do + @path = "C:\\" + end + + it "should convert to \'file:///c:/\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path '/one/two/'" do + before do + @path = '/one/two/' + end + + it "should convert to " + + "\'file:///one/two/\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///one/two/" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path " + + "'c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/windows/My%20Documents%20100%20/foo.txt" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path " + + "'file://c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file://c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/windows/My%20Documents%20100%20/foo.txt" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path " + + "'file:c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file:c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/windows/My%20Documents%20100%20/foo.txt" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path " + + "'file:/c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file:/c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/windows/My%20Documents%20100%20/foo.txt" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given the path " + + "'file:///c|/windows/My%20Documents%20100%20/foo.txt'" do + before do + @path = "file:///c|/windows/My%20Documents%20100%20/foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "file:///c:/windows/My%20Documents%20100%20/foo.txt" + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + @uri.origin.should == 'file://' + end +end + +describe Addressable::URI, "when given an http protocol URI" do + before do + @path = "http://example.com/" + end + + it "should not do any conversion at all" do + @uri = Addressable::URI.convert_path(@path) + @uri.to_str.should == "http://example.com/" + end +end + +class SuperString + def initialize(string) + @string = string.to_s + end + + def to_str + return @string + end +end + +describe Addressable::URI, "when parsing a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.parse(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.parse(42) + end).should raise_error(TypeError, "Can't convert Fixnum into String.") + end + + it "should correctly parse heuristically anything with a 'to_str' method" do + Addressable::URI.heuristic_parse(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.heuristic_parse(42) + end).should raise_error(TypeError, "Can't convert Fixnum into String.") + end +end + +describe Addressable::URI, "when form encoding a hash" do + it "should result in correct percent encoded sequence" do + Addressable::URI.form_encode( + [["&one", "/1"], ["=two", "?2"], [":three", "#3"]] + ).should == "%26one=%2F1&%3Dtwo=%3F2&%3Athree=%233" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.form_encode( + {"q" => "one two three"} + ).should == "q=one+two+three" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.form_encode( + {"key" => nil} + ).should == "key=" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.form_encode( + {"q" => ["one", "two", "three"]} + ).should == "q=one&q=two&q=three" + end + + it "should result in correctly encoded newlines" do + Addressable::URI.form_encode( + {"text" => "one\ntwo\rthree\r\nfour\n\r"} + ).should == "text=one%0D%0Atwo%0D%0Athree%0D%0Afour%0D%0A%0D%0A" + end + + it "should result in a sorted percent encoded sequence" do + Addressable::URI.form_encode( + [["a", "1"], ["dup", "3"], ["dup", "2"]], true + ).should == "a=1&dup=2&dup=3" + end +end + +describe Addressable::URI, "when form encoding a non-Array object" do + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.form_encode(42) + end).should raise_error(TypeError, "Can't convert Fixnum into Array.") + end +end + +describe Addressable::URI, "when form unencoding a string" do + it "should result in correct values" do + Addressable::URI.form_unencode( + "%26one=%2F1&%3Dtwo=%3F2&%3Athree=%233" + ).should == [["&one", "/1"], ["=two", "?2"], [":three", "#3"]] + end + + it "should result in correct values" do + Addressable::URI.form_unencode( + "q=one+two+three" + ).should == [["q", "one two three"]] + end + + it "should result in correct values" do + Addressable::URI.form_unencode( + "text=one%0D%0Atwo%0D%0Athree%0D%0Afour%0D%0A%0D%0A" + ).should == [["text", "one\ntwo\nthree\nfour\n\n"]] + end + + it "should result in correct values" do + Addressable::URI.form_unencode( + "a=1&dup=2&dup=3" + ).should == [["a", "1"], ["dup", "2"], ["dup", "3"]] + end + + it "should result in correct values" do + Addressable::URI.form_unencode( + "key" + ).should == [["key", nil]] + end + + it "should result in correct values" do + Addressable::URI.form_unencode("GivenName=Ren%C3%A9").should == + [["GivenName", "René"]] + end +end + +describe Addressable::URI, "when form unencoding a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.form_unencode(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.form_unencode(42) + end).should raise_error(TypeError, "Can't convert Fixnum into String.") + end +end + +describe Addressable::URI, "when normalizing a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.normalize_component(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.normalize_component(42) + end).should raise_error(TypeError, "Can't convert Fixnum into String.") + end + + it "should raise a TypeError for objects than cannot be converted" do + (lambda do + Addressable::URI.normalize_component("component", 42) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when normalizing a path with an encoded slash" do + it "should result in correct percent encoded sequence" do + Addressable::URI.parse("/path%2Fsegment/").normalize.path.should == + "/path%2Fsegment/" + end +end + +describe Addressable::URI, "when normalizing a partially encoded string" do + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component( + "partially % encoded%21" + ).should == "partially%20%25%20encoded!" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component( + "partially %25 encoded!" + ).should == "partially%20%25%20encoded!" + end +end + +describe Addressable::URI, "when normalizing a unicode sequence" do + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component( + "/C%CC%A7" + ).should == "/%C3%87" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component( + "/%C3%87" + ).should == "/%C3%87" + end +end + +describe Addressable::URI, "when normalizing a multibyte string" do + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component("günther").should == + "g%C3%BCnther" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component("g%C3%BCnther").should == + "g%C3%BCnther" + end +end + +describe Addressable::URI, "when normalizing a string but leaving some characters encoded" do + it "should result in correct percent encoded sequence" do + Addressable::URI.normalize_component("%58X%59Y%5AZ", "0-9a-zXY", "Y").should == + "XX%59Y%5A%5A" + end + + it "should not modify the character class" do + character_class = "0-9a-zXY" + + character_class_copy = character_class.dup + + Addressable::URI.normalize_component("%58X%59Y%5AZ", character_class, "Y") + + character_class.should == character_class_copy + end +end + +describe Addressable::URI, "when encoding a string with existing encodings to upcase" do + it "should result in correct percent encoded sequence" do + Addressable::URI.encode_component("JK%4c", "0-9A-IKM-Za-z%", "L").should == "%4AK%4C" + end +end + +describe Addressable::URI, "when encoding a multibyte string" do + it "should result in correct percent encoded sequence" do + Addressable::URI.encode_component("günther").should == "g%C3%BCnther" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.encode_component( + "günther", /[^a-zA-Z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\-\.\_\~]/ + ).should == "g%C3%BCnther" + end +end + +describe Addressable::URI, "when form encoding a multibyte string" do + it "should result in correct percent encoded sequence" do + Addressable::URI.form_encode({"GivenName" => "René"}).should == + "GivenName=Ren%C3%A9" + end +end + +describe Addressable::URI, "when encoding a string with ASCII chars 0-15" do + it "should result in correct percent encoded sequence" do + Addressable::URI.encode_component("one\ntwo").should == "one%0Atwo" + end + + it "should result in correct percent encoded sequence" do + Addressable::URI.encode_component( + "one\ntwo", /[^a-zA-Z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\-\.\_\~]/ + ).should == "one%0Atwo" + end +end + +describe Addressable::URI, "when unencoding a multibyte string" do + it "should result in correct percent encoded sequence" do + Addressable::URI.unencode_component("g%C3%BCnther").should == "günther" + end + + it "should consistently use UTF-8 internally" do + Addressable::URI.unencode_component("ski=%BA%DAɫ").should == "ski=\xBA\xDAɫ" + end + + it "should result in correct percent encoded sequence as a URI" do + Addressable::URI.unencode( + "/path?g%C3%BCnther", ::Addressable::URI + ).should == Addressable::URI.new( + :path => "/path", :query => "günther" + ) + end +end + +describe Addressable::URI, "when partially unencoding a string" do + it "should unencode all characters by default" do + Addressable::URI.unencode('%%25~%7e+%2b', String).should == '%%~~++' + end + + it "should unencode characters not in leave_encoded" do + Addressable::URI.unencode('%%25~%7e+%2b', String, '~').should == '%%~%7e++' + end + + it "should leave characters in leave_encoded alone" do + Addressable::URI.unencode('%%25~%7e+%2b', String, '%~+').should == '%%25~%7e+%2b' + end +end + +describe Addressable::URI, "when unencoding a bogus object" do + it "should raise a TypeError" do + (lambda do + Addressable::URI.unencode_component(42) + end).should raise_error(TypeError) + end + + it "should raise a TypeError" do + (lambda do + Addressable::URI.unencode("/path?g%C3%BCnther", Integer) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when encoding a bogus object" do + it "should raise a TypeError" do + (lambda do + Addressable::URI.encode(Object.new) + end).should raise_error(TypeError) + end + + it "should raise a TypeError" do + (lambda do + Addressable::URI.normalized_encode(Object.new) + end).should raise_error(TypeError) + end + + it "should raise a TypeError" do + (lambda do + Addressable::URI.encode_component("günther", Object.new) + end).should raise_error(TypeError) + end + + it "should raise a TypeError" do + (lambda do + Addressable::URI.encode_component(Object.new) + end).should raise_error(TypeError) + end +end + +describe Addressable::URI, "when given the input " + + "'http://example.com/'" do + before do + @input = "http://example.com/" + end + + it "should heuristically parse to 'http://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com/" + end + + it "should not raise error when frozen" do + (lambda do + Addressable::URI.heuristic_parse(@input).freeze.to_s + end).should_not raise_error + end +end + +describe Addressable::URI, "when given the input " + + "'https://example.com/'" do + before do + @input = "https://example.com/" + end + + it "should heuristically parse to 'https://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "https://example.com/" + end +end + +describe Addressable::URI, "when given the input " + + "'http:example.com/'" do + before do + @input = "http:example.com/" + end + + it "should heuristically parse to 'http://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com/" + end + + it "should heuristically parse to 'http://example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + @uri.to_s.should == "http://example.com/" + end +end + +describe Addressable::URI, "when given the input " + + "'https:example.com/'" do + before do + @input = "https:example.com/" + end + + it "should heuristically parse to 'https://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "https://example.com/" + end + + it "should heuristically parse to 'https://example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + @uri.to_s.should == "https://example.com/" + end +end + +describe Addressable::URI, "when given the input " + + "'http://example.com/example.com/'" do + before do + @input = "http://example.com/example.com/" + end + + it "should heuristically parse to 'http://example.com/example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com/example.com/" + end +end + +describe Addressable::URI, "when given the input " + + "'/path/to/resource'" do + before do + @input = "/path/to/resource" + end + + it "should heuristically parse to '/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "/path/to/resource" + end +end + +describe Addressable::URI, "when given the input " + + "'relative/path/to/resource'" do + before do + @input = "relative/path/to/resource" + end + + it "should heuristically parse to 'relative/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "relative/path/to/resource" + end +end + +describe Addressable::URI, "when given the input " + + "'example.com'" do + before do + @input = "example.com" + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com" + end +end + +describe Addressable::URI, "when given the input " + + "'example.com' and a scheme hint of 'ftp'" do + before do + @input = "example.com" + @hints = {:scheme => 'ftp'} + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input, @hints) + @uri.to_s.should == "ftp://example.com" + end +end + +describe Addressable::URI, "when given the input " + + "'example.com:21' and a scheme hint of 'ftp'" do + before do + @input = "example.com:21" + @hints = {:scheme => 'ftp'} + end + + it "should heuristically parse to 'http://example.com:21'" do + @uri = Addressable::URI.heuristic_parse(@input, @hints) + @uri.to_s.should == "ftp://example.com:21" + end +end + +describe Addressable::URI, "when given the input " + + "'example.com/path/to/resource'" do + before do + @input = "example.com/path/to/resource" + end + + it "should heuristically parse to 'http://example.com/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com/path/to/resource" + end +end + +describe Addressable::URI, "when given the input " + + "'http:///example.com'" do + before do + @input = "http:///example.com" + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com" + end +end + +describe Addressable::URI, "when given the input " + + "'feed:///example.com'" do + before do + @input = "feed:///example.com" + end + + it "should heuristically parse to 'feed://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "feed://example.com" + end +end + +describe Addressable::URI, "when given the input " + + "'file://path/to/resource/'" do + before do + @input = "file://path/to/resource/" + end + + it "should heuristically parse to 'file:///path/to/resource/'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "file:///path/to/resource/" + end +end + +describe Addressable::URI, "when given the input " + + "'feed://http://example.com'" do + before do + @input = "feed://http://example.com" + end + + it "should heuristically parse to 'feed:http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "feed:http://example.com" + end +end + +describe Addressable::URI, "when given the input " + + "::URI.parse('http://example.com')" do + before do + @input = ::URI.parse('http://example.com') + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + @uri.to_s.should == "http://example.com" + end +end + +describe Addressable::URI, "when assigning query values" do + before do + @uri = Addressable::URI.new + end + + it "should correctly assign {:a => 'a', :b => ['c', 'd', 'e']}" do + @uri.query_values = {:a => "a", :b => ["c", "d", "e"]} + @uri.query.should == "a=a&b=c&b=d&b=e" + end + + it "should raise an error attempting to assign {'a' => {'b' => ['c']}}" do + (lambda do + @uri.query_values = { 'a' => {'b' => ['c'] } } + end).should raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:b => '2', :a => {:c => '1'}}" do + (lambda do + @uri.query_values = {:b => '2', :a => {:c => '1'}} + end).should raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => [{:c => 'c', :d => 'd'}, " + + "{:e => 'e', :f => 'f'}]}" do + (lambda do + @uri.query_values = { + :a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}] + } + end).should raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => [{:c => true, :d => 'd'}, " + + "{:e => 'e', :f => 'f'}]}" do + (lambda do + @uri.query_values = { + :a => 'a', :b => [{:c => true, :d => 'd'}, {:e => 'e', :f => 'f'}] + } + end).should raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => {:c => true, :d => 'd'}}" do + (lambda do + @uri.query_values = { + :a => 'a', :b => {:c => true, :d => 'd'} + } + end).should raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => {:c => true, :d => 'd'}}" do + (lambda do + @uri.query_values = { + :a => 'a', :b => {:c => true, :d => 'd'} + } + end).should raise_error(TypeError) + end + + it "should correctly assign {:a => 1, :b => 1.5}" do + @uri.query_values = { :a => 1, :b => 1.5 } + @uri.query.should == "a=1&b=1.5" + end + + it "should raise an error attempting to assign " + + "{:z => 1, :f => [2, {999.1 => [3,'4']}, ['h', 'i']], " + + ":a => {:b => ['c', 'd'], :e => true, :y => 0.5}}" do + (lambda do + @uri.query_values = { + :z => 1, + :f => [ 2, {999.1 => [3,'4']}, ['h', 'i'] ], + :a => { :b => ['c', 'd'], :e => true, :y => 0.5 } + } + end).should raise_error(TypeError) + end + + it "should correctly assign {}" do + @uri.query_values = {} + @uri.query.should == '' + end + + it "should correctly assign nil" do + @uri.query_values = nil + @uri.query.should == nil + end + + it "should correctly sort {'ab' => 'c', :ab => 'a', :a => 'x'}" do + @uri.query_values = {'ab' => 'c', :ab => 'a', :a => 'x'} + @uri.query.should == "a=x&ab=a&ab=c" + end + + it "should correctly assign " + + "[['b', 'c'], ['b', 'a'], ['a', 'a']]" do + # Order can be guaranteed in this format, so preserve it. + @uri.query_values = [['b', 'c'], ['b', 'a'], ['a', 'a']] + @uri.query.should == "b=c&b=a&a=a" + end + + it "should preserve query string order" do + query_string = (('a'..'z').to_a.reverse.map { |e| "#{e}=#{e}" }).join("&") + @uri.query = query_string + original_uri = @uri.to_s + @uri.query_values = @uri.query_values(Array) + @uri.to_s.should == original_uri + end + + describe 'when a hash with mixed types is assigned to query_values' do + it 'should not raise an error' do + pending 'Issue #94' do + expect { subject.query_values = { "page" => "1", :page => 2 } }.to_not raise_error + end + end + end +end + +describe Addressable::URI, "when assigning path values" do + before do + @uri = Addressable::URI.new + end + + it "should correctly assign paths containing colons" do + @uri.path = "acct:bob@sporkmonger.com" + @uri.path.should == "acct:bob@sporkmonger.com" + @uri.normalize.to_str.should == "acct%2Fbob@sporkmonger.com" + (lambda { @uri.to_s }).should raise_error( + Addressable::URI::InvalidURIError + ) + end + + it "should correctly assign paths containing colons" do + @uri.path = "/acct:bob@sporkmonger.com" + @uri.authority = "example.com" + @uri.normalize.to_str.should == "//example.com/acct:bob@sporkmonger.com" + end + + it "should correctly assign paths containing colons" do + @uri.path = "acct:bob@sporkmonger.com" + @uri.scheme = "something" + @uri.normalize.to_str.should == "something:acct:bob@sporkmonger.com" + end + + it "should not allow relative paths to be assigned on absolute URIs" do + (lambda do + @uri.scheme = "http" + @uri.host = "example.com" + @uri.path = "acct:bob@sporkmonger.com" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow relative paths to be assigned on absolute URIs" do + (lambda do + @uri.path = "acct:bob@sporkmonger.com" + @uri.scheme = "http" + @uri.host = "example.com" + end).should raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow relative paths to be assigned on absolute URIs" do + (lambda do + @uri.path = "uuid:0b3ecf60-3f93-11df-a9c3-001f5bfffe12" + @uri.scheme = "urn" + end).should_not raise_error + end +end + +describe Addressable::URI, "when initializing a subclass of Addressable::URI" do + before do + @uri = Class.new(Addressable::URI).new + end + + it "should have the same class after being parsed" do + @uri.class.should == Addressable::URI.parse(@uri).class + end + + it "should have the same class as its duplicate" do + @uri.class.should == @uri.dup.class + end + + it "should have the same class after being normalized" do + @uri.class.should == @uri.normalize.class + end + + it "should have the same class after being merged" do + @uri.class.should == @uri.merge(:path => 'path').class + end + + it "should have the same class after being joined" do + @uri.class.should == @uri.join('path').class + end +end diff --git a/.gems/gems/addressable-2.3.6/spec/spec_helper.rb b/.gems/gems/addressable-2.3.6/spec/spec_helper.rb new file mode 100644 index 0000000..e0020ba --- /dev/null +++ b/.gems/gems/addressable-2.3.6/spec/spec_helper.rb @@ -0,0 +1,6 @@ +begin + require 'coveralls' + Coveralls.wear! +rescue LoadError + warn "warning: coveralls gem not found; skipping Coveralls" +end diff --git a/.gems/gems/addressable-2.3.6/tasks/clobber.rake b/.gems/gems/addressable-2.3.6/tasks/clobber.rake new file mode 100644 index 0000000..093ce81 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/clobber.rake @@ -0,0 +1,2 @@ +desc "Remove all build products" +task "clobber" diff --git a/.gems/gems/addressable-2.3.6/tasks/gem.rake b/.gems/gems/addressable-2.3.6/tasks/gem.rake new file mode 100644 index 0000000..8596712 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/gem.rake @@ -0,0 +1,86 @@ +require "rubygems/package_task" + +namespace :gem do + GEM_SPEC = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.summary = PKG_SUMMARY + s.description = PKG_DESCRIPTION + + s.files = PKG_FILES.to_a + + s.has_rdoc = true + s.extra_rdoc_files = %w( README.md ) + s.rdoc_options.concat ["--main", "README.md"] + + if !s.respond_to?(:add_development_dependency) + puts "Cannot build Gem with this version of RubyGems." + exit(1) + end + + s.add_development_dependency("rake", ">= 0.7.3") + s.add_development_dependency("rspec", ">= 2.9.0") + s.add_development_dependency("launchy", ">= 0.3.2") + + s.require_path = "lib" + + s.author = "Bob Aman" + s.email = "bob@sporkmonger.com" + s.homepage = RUBY_FORGE_URL + s.rubyforge_project = RUBY_FORGE_PROJECT + s.license = "Apache License 2.0" + end + + Gem::PackageTask.new(GEM_SPEC) do |p| + p.gem_spec = GEM_SPEC + p.need_tar = true + p.need_zip = true + end + + desc "Generates .gemspec file" + task :gemspec do + spec_string = GEM_SPEC.to_ruby + + begin + Thread.new { eval("$SAFE = 3\n#{spec_string}", binding) }.join + rescue + abort "unsafe gemspec: #{$!}" + else + File.open("#{GEM_SPEC.name}.gemspec", 'w') do |file| + file.write spec_string + end + end + end + + desc "Show information about the gem" + task :debug do + puts GEM_SPEC.to_ruby + end + + desc "Install the gem" + task :install => ["clobber", "gem:package"] do + sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}" + end + + desc "Uninstall the gem" + task :uninstall do + installed_list = Gem.source_index.find_name(PKG_NAME) + if installed_list && + (installed_list.collect { |s| s.version.to_s}.include?(PKG_VERSION)) + sh( + "#{SUDO} gem uninstall --version '#{PKG_VERSION}' " + + "--ignore-dependencies --executables #{PKG_NAME}" + ) + end + end + + desc "Reinstall the gem" + task :reinstall => [:uninstall, :install] +end + +desc "Alias to gem:package" +task "gem" => "gem:package" + +task "gem:release" => "gem:gemspec" + +task "clobber" => ["gem:clobber_package"] diff --git a/.gems/gems/addressable-2.3.6/tasks/git.rake b/.gems/gems/addressable-2.3.6/tasks/git.rake new file mode 100644 index 0000000..092032b --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/git.rake @@ -0,0 +1,45 @@ +namespace :git do + namespace :tag do + desc "List tags from the Git repository" + task :list do + tags = `git tag -l` + tags.gsub!("\r", "") + tags = tags.split("\n").sort {|a, b| b <=> a } + puts tags.join("\n") + end + + desc "Create a new tag in the Git repository" + task :create do + changelog = File.open("CHANGELOG.md", "r") { |file| file.read } + puts "-" * 80 + puts changelog + puts "-" * 80 + puts + + v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z" + abort "Versions don't match #{v} vs #{PKG_VERSION}" if v != PKG_VERSION + + git_status = `git status` + if git_status !~ /nothing to commit \(working directory clean\)/ + abort "Working directory isn't clean." + end + + tag = "#{PKG_NAME}-#{PKG_VERSION}" + msg = "Release #{PKG_NAME}-#{PKG_VERSION}" + + existing_tags = `git tag -l #{PKG_NAME}-*`.split('\n') + if existing_tags.include?(tag) + warn("Tag already exists, deleting...") + unless system "git tag -d #{tag}" + abort "Tag deletion failed." + end + end + puts "Creating git tag '#{tag}'..." + unless system "git tag -a -m \"#{msg}\" #{tag}" + abort "Tag creation failed." + end + end + end +end + +task "gem:release" => "git:tag:create" diff --git a/.gems/gems/addressable-2.3.6/tasks/metrics.rake b/.gems/gems/addressable-2.3.6/tasks/metrics.rake new file mode 100644 index 0000000..41fc5c2 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/metrics.rake @@ -0,0 +1,22 @@ +namespace :metrics do + task :lines do + lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 + for file_name in FileList["lib/**/*.rb"] + f = File.open(file_name) + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end + puts "L: #{sprintf("%4d", lines)}, " + + "LOC #{sprintf("%4d", codelines)} | #{file_name}" + total_lines += lines + total_codelines += codelines + + lines, codelines = 0, 0 + end + + puts "Total: Lines #{total_lines}, LOC #{total_codelines}" + end +end diff --git a/.gems/gems/addressable-2.3.6/tasks/rspec.rake b/.gems/gems/addressable-2.3.6/tasks/rspec.rake new file mode 100644 index 0000000..6b7e3a7 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/rspec.rake @@ -0,0 +1,58 @@ +require "rspec/core/rake_task" + +namespace :spec do + RSpec::Core::RakeTask.new(:rcov) do |t| + t.pattern = FileList['spec/**/*_spec.rb'] + t.rspec_opts = ['--color', '--format', 'documentation'] + + t.rcov = RCOV_ENABLED + t.rcov_opts = [ + '--exclude', 'lib\\/compat', + '--exclude', 'spec', + '--exclude', '\\.rvm\\/gems', + '--exclude', '1\\.8\\/gems', + '--exclude', '1\\.9\\/gems', + '--exclude', '\\.rvm', + '--exclude', '\\/Library\\/Ruby', + '--exclude', 'addressable\\/idna' # environment dependant + ] + end + + RSpec::Core::RakeTask.new(:normal) do |t| + t.pattern = FileList['spec/**/*_spec.rb'].exclude(/compat/) + t.rspec_opts = ['--color', '--format', 'documentation'] + t.rcov = false + end + + RSpec::Core::RakeTask.new(:all) do |t| + t.pattern = FileList['spec/**/*_spec.rb'] + t.rspec_opts = ['--color', '--format', 'documentation'] + t.rcov = false + end + + desc "Generate HTML Specdocs for all specs" + RSpec::Core::RakeTask.new(:specdoc) do |t| + specdoc_path = File.expand_path( + File.join(File.dirname(__FILE__), '..', 'documentation') + ) + Dir.mkdir(specdoc_path) if !File.exist?(specdoc_path) + + output_file = File.join(specdoc_path, 'index.html') + t.pattern = FileList['spec/**/*_spec.rb'] + t.rspec_opts = ["--format", "\"html:#{output_file}\"", "--diff"] + t.fail_on_error = false + end + + namespace :rcov do + desc "Browse the code coverage report." + task :browse => "spec:rcov" do + require "launchy" + Launchy::Browser.run("coverage/index.html") + end + end +end + +desc "Alias to spec:normal" +task "spec" => "spec:normal" + +task "clobber" => ["spec:clobber_rcov"] diff --git a/.gems/gems/addressable-2.3.6/tasks/rubyforge.rake b/.gems/gems/addressable-2.3.6/tasks/rubyforge.rake new file mode 100644 index 0000000..61d2ddb --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/rubyforge.rake @@ -0,0 +1,73 @@ +namespace :gem do + desc 'Package and upload to RubyForge' + task :release => ["gem:package", "gem:gemspec"] do |t| + require 'rubyforge' + + v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z' + abort "Versions don't match #{v} vs #{PROJ.version}" if v != PKG_VERSION + pkg = "pkg/#{GEM_SPEC.full_name}" + + rf = RubyForge.new + rf.configure + puts 'Logging in...' + rf.login + + c = rf.userconfig + changelog = File.open("CHANGELOG.md") { |file| file.read } + c['release_changes'] = changelog + c['preformatted'] = true + + files = ["#{pkg}.tgz", "#{pkg}.zip", "#{pkg}.gem"] + + puts "Releasing #{PKG_NAME} v. #{PKG_VERSION}" + rf.add_release RUBY_FORGE_PROJECT, PKG_NAME, PKG_VERSION, *files + end +end + +namespace :spec do + desc "Publish specdoc to RubyForge" + task :release => ["spec:specdoc"] do + require "rake/contrib/sshpublisher" + require "yaml" + + config = YAML.load( + File.read(File.expand_path('~/.rubyforge/user-config.yml')) + ) + host = "#{config['username']}@rubyforge.org" + remote_dir = RUBY_FORGE_PATH + "/specdoc" + local_dir = "specdoc" + Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload + end + + namespace :rcov do + desc "Publish coverage report to RubyForge" + task :release => ["spec:rcov"] do + require "rake/contrib/sshpublisher" + require "yaml" + + config = YAML.load( + File.read(File.expand_path('~/.rubyforge/user-config.yml')) + ) + host = "#{config['username']}@rubyforge.org" + remote_dir = RUBY_FORGE_PATH + "/coverage" + local_dir = "coverage" + Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload + end + end +end + +namespace :website do + desc "Publish website to RubyForge" + task :release => ["doc:release", "spec:release", "spec:rcov:release"] do + require "rake/contrib/sshpublisher" + require "yaml" + + config = YAML.load( + File.read(File.expand_path('~/.rubyforge/user-config.yml')) + ) + host = "#{config['username']}@rubyforge.org" + remote_dir = RUBY_FORGE_PATH + local_dir = "website" + Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload + end +end diff --git a/.gems/gems/addressable-2.3.6/tasks/yard.rake b/.gems/gems/addressable-2.3.6/tasks/yard.rake new file mode 100644 index 0000000..68e4491 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/tasks/yard.rake @@ -0,0 +1,27 @@ +require "rake" + +begin + require "yard" + require "yard/rake/yardoc_task" + + namespace :doc do + desc "Generate Yardoc documentation" + YARD::Rake::YardocTask.new do |yardoc| + yardoc.name = "yard" + yardoc.options = ["--verbose", "--markup", "markdown"] + yardoc.files = FileList[ + "lib/**/*.rb", "ext/**/*.c", + "README.md", "CHANGELOG.md", "LICENSE.txt" + ].exclude(/idna/) + end + end + + task "clobber" => ["doc:clobber_yard"] + + desc "Alias to doc:yard" + task "doc" => "doc:yard" +rescue LoadError + # If yard isn't available, it's not the end of the world + desc "Alias to doc:rdoc" + task "doc" => "doc:rdoc" +end diff --git a/.gems/gems/addressable-2.3.6/website/index.html b/.gems/gems/addressable-2.3.6/website/index.html new file mode 100644 index 0000000..ba92bd6 --- /dev/null +++ b/.gems/gems/addressable-2.3.6/website/index.html @@ -0,0 +1,110 @@ + + + + + Addressable + + + +

Addressable

+
+

+ Addressable is a replacement for the URI implementation that is part + of Ruby's standard library. It more closely conforms to the relevant + RFCs and adds support for IRIs and URI templates. +

+ +

+ You know what to do: +

+

+ sudo gem install addressable +

+

+ Alternatively, you can: +

+

+ + git submodule add + git://github.com/sporkmonger/addressable.git + vendor/gems/addressable + +

+

+ Addressable works in Ruby 1.8.x, 1.9.x, and JRuby. +

+
+ + diff --git a/.gems/gems/buftok-0.2.0/CONTRIBUTING.md b/.gems/gems/buftok-0.2.0/CONTRIBUTING.md new file mode 100644 index 0000000..06c35bb --- /dev/null +++ b/.gems/gems/buftok-0.2.0/CONTRIBUTING.md @@ -0,0 +1,49 @@ +## Contributing +In the spirit of [free software][free-sw], **everyone** is encouraged to help +improve this project. Here are some ways *you* can contribute: + +[free-sw]: http://www.fsf.org/licensing/essays/free-sw.html + +* Use alpha, beta, and pre-release versions. +* Report bugs. +* Suggest new features. +* Write or edit documentation. +* Write specifications. +* Write code (**no patch is too small**: fix typos, add comments, clean up + inconsistent whitespace). +* Refactor code. +* Fix [issues][]. +* Review patches. + +[issues]: https://github.com/sferik/buftok/issues + +## Submitting an Issue +We use the [GitHub issue tracker][issues] to track bugs and features. Before +submitting a bug report or feature request, check to make sure it hasn't +already been submitted. When submitting a bug report, please include a [Gist][] +that includes a stack trace and any details that may be necessary to reproduce +the bug, including your gem version, Ruby version, and operating system. +Ideally, a bug report should include a pull request with failing specs. + +[gist]: https://gist.github.com/ + +## Submitting a Pull Request +1. [Fork the repository.][fork] +2. [Create a topic branch.][branch] +3. Add specs for your unimplemented feature or bug fix. +4. Run `bundle exec rake spec`. If your specs pass, return to step 3. +5. Implement your feature or bug fix. +6. Run `bundle exec rake spec`. If your specs fail, return to step 5. +7. Run `open coverage/index.html`. If your changes are not completely covered + by your tests, return to step 3. +8. Run `RUBYOPT=W2 bundle exec rake spec 2>&1 | grep buftok`. If your changes + produce any warnings, return to step 5. +9. Add documentation for your feature or bug fix. +10. Run `bundle exec rake yard`. If your changes are not 100% documented, go + back to step 9. +11. Commit and push your changes. +12. [Submit a pull request.][pr] + +[fork]: http://help.github.com/fork-a-repo/ +[branch]: http://learn.github.com/p/branching.html +[pr]: http://help.github.com/send-pull-requests/ diff --git a/.gems/gems/buftok-0.2.0/Gemfile b/.gems/gems/buftok-0.2.0/Gemfile new file mode 100644 index 0000000..976088b --- /dev/null +++ b/.gems/gems/buftok-0.2.0/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'rake' +gem 'rdoc' + +gemspec diff --git a/.gems/gems/buftok-0.2.0/LICENSE.md b/.gems/gems/buftok-0.2.0/LICENSE.md new file mode 100644 index 0000000..426810a --- /dev/null +++ b/.gems/gems/buftok-0.2.0/LICENSE.md @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + + 1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + + 2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b) use the modified software only within your corporation or + organization. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the author. + + 3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a) distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b) accompany the distribution with the machine-readable source of + the software. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the author. + + 4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + + 5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + + 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/.gems/gems/buftok-0.2.0/README.md b/.gems/gems/buftok-0.2.0/README.md new file mode 100644 index 0000000..afba76b --- /dev/null +++ b/.gems/gems/buftok-0.2.0/README.md @@ -0,0 +1,48 @@ +# BufferedTokenizer + +[![Gem Version](https://badge.fury.io/rb/buftok.png)][gem] +[![Build Status](https://travis-ci.org/sferik/buftok.png?branch=master)][travis] +[![Dependency Status](https://gemnasium.com/sferik/buftok.png?travis)][gemnasium] +[![Code Climate](https://codeclimate.com/github/sferik/buftok.png)][codeclimate] + +[gem]: https://rubygems.org/gems/buftok +[travis]: https://travis-ci.org/sferik/buftok +[gemnasium]: https://gemnasium.com/sferik/buftok +[codeclimate]: https://codeclimate.com/github/sferik/buftok + +###### Statefully split input data by a specifiable token + +BufferedTokenizer takes a delimiter upon instantiation, or acts line-based by +default. It allows input to be spoon-fed from some outside source which +receives arbitrary length datagrams which may-or-may-not contain the token by +which entities are delimited. In this respect it's ideally paired with +something like [EventMachine][]. + +[EventMachine]: http://rubyeventmachine.com/ + +## Supported Ruby Versions +This library aims to support and is [tested against][travis] the following Ruby +implementations: + +* Ruby 1.8.7 +* Ruby 1.9.2 +* Ruby 1.9.3 +* Ruby 2.0.0 + +If something doesn't work on one of these interpreters, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby +implementations, however support will only be provided for the versions listed +above. + +If you would like this library to support another Ruby version, you may +volunteer to be a maintainer. Being a maintainer entails making sure all tests +run and pass on that implementation. When something breaks on your +implementation, you will be responsible for providing patches in a timely +fashion. If critical issues for a particular implementation exist at the time +of a major release, support for that Ruby version may be dropped. + +## Copyright +Copyright (c) 2006-2013 Tony Arcieri, Martin Emde, Erik Michaels-Ober. +Distributed under the [Ruby license][license]. +[license]: http://www.ruby-lang.org/en/LICENSE.txt diff --git a/.gems/gems/buftok-0.2.0/Rakefile b/.gems/gems/buftok-0.2.0/Rakefile new file mode 100644 index 0000000..b2ba2d0 --- /dev/null +++ b/.gems/gems/buftok-0.2.0/Rakefile @@ -0,0 +1,66 @@ +require 'bundler' +require 'rdoc/task' +require 'rake/testtask' + +task :default => :test + +Bundler::GemHelper.install_tasks + +RDoc::Task.new do |task| + task.rdoc_dir = 'doc' + task.title = 'BufferedTokenizer' + task.rdoc_files.include('lib/**/*.rb') +end + +Rake::TestTask.new :test do |t| + t.libs << 'lib' + t.test_files = FileList['test/**/*.rb'] +end + +desc "Benchmark the current implementation" +task :bench do + require 'benchmark' + require File.expand_path('lib/buftok', File.dirname(__FILE__)) + + n = 50000 + delimiter = "\n\n" + + frequency1 = 1000 + puts "generating #{n} strings, with #{delimiter.inspect} every #{frequency1} strings..." + data1 = (0...n).map do |i| + (((i % frequency1 == 1) ? "\n" : "") + + ("s" * i) + + ((i % frequency1 == 0) ? "\n" : "")).freeze + end + + frequency2 = 10 + puts "generating #{n} strings, with #{delimiter.inspect} every #{frequency2} strings..." + data2 = (0...n).map do |i| + (((i % frequency2 == 1) ? "\n" : "") + + ("s" * i) + + ((i % frequency2 == 0) ? "\n" : "")).freeze + end + + Benchmark.bmbm do |x| + x.report("1 char, freq: #{frequency1}") do + bt1 = BufferedTokenizer.new + n.times { |i| bt1.extract(data1[i]) } + end + + x.report("2 char, freq: #{frequency1}") do + bt2 = BufferedTokenizer.new(delimiter) + n.times { |i| bt2.extract(data1[i]) } + end + + x.report("1 char, freq: #{frequency2}") do + bt3 = BufferedTokenizer.new + n.times { |i| bt3.extract(data2[i]) } + end + + x.report("2 char, freq: #{frequency2}") do + bt4 = BufferedTokenizer.new(delimiter) + n.times { |i| bt4.extract(data2[i]) } + end + + end +end diff --git a/.gems/gems/buftok-0.2.0/buftok.gemspec b/.gems/gems/buftok-0.2.0/buftok.gemspec new file mode 100644 index 0000000..e108dd3 --- /dev/null +++ b/.gems/gems/buftok-0.2.0/buftok.gemspec @@ -0,0 +1,17 @@ +Gem::Specification.new do |spec| + spec.add_development_dependency 'bundler', '~> 1.0' + spec.authors = ["Tony Arcieri", "Martin Emde", "Erik Michaels-Ober"] + spec.description = %q{BufferedTokenizer extracts token delimited entities from a sequence of arbitrary inputs} + spec.email = "sferik@gmail.com" + spec.files = %w(CONTRIBUTING.md Gemfile LICENSE.md README.md Rakefile buftok.gemspec) + spec.files += Dir.glob("lib/**/*.rb") + spec.files += Dir.glob("test/**/*.rb") + spec.test_files = spec.files.grep(%r{^test/}) + spec.homepage = "https://github.com/sferik/buftok" + spec.licenses = ['MIT'] + spec.name = "buftok" + spec.require_paths = ["lib"] + spec.required_rubygems_version = '>= 1.3.5' + spec.summary = spec.description + spec.version = "0.2.0" +end diff --git a/.gems/gems/buftok-0.2.0/lib/buftok.rb b/.gems/gems/buftok-0.2.0/lib/buftok.rb new file mode 100644 index 0000000..caf4f77 --- /dev/null +++ b/.gems/gems/buftok-0.2.0/lib/buftok.rb @@ -0,0 +1,59 @@ +# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based +# by default. It allows input to be spoon-fed from some outside source which +# receives arbitrary length datagrams which may-or-may-not contain the token +# by which entities are delimited. In this respect it's ideally paired with +# something like EventMachine (http://rubyeventmachine.com/). +class BufferedTokenizer + # New BufferedTokenizers will operate on lines delimited by a delimiter, + # which is by default the global input delimiter $/ ("\n"). + # + # The input buffer is stored as an array. This is by far the most efficient + # approach given language constraints (in C a linked list would be a more + # appropriate data structure). Segments of input data are stored in a list + # which is only joined when a token is reached, substantially reducing the + # number of objects required for the operation. + def initialize(delimiter = $/) + @delimiter = delimiter + @input = [] + @tail = '' + @trim = @delimiter.length - 1 + end + + # Extract takes an arbitrary string of input data and returns an array of + # tokenized entities, provided there were any available to extract. This + # makes for easy processing of datagrams using a pattern like: + # + # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ... + # + # Using -1 makes split to return "" if the token is at the end of + # the string, meaning the last element is the start of the next chunk. + def extract(data) + if @trim > 0 + tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short + data = tail_end + data if tail_end + end + + @input << @tail + entities = data.split(@delimiter, -1) + @tail = entities.shift + + unless entities.empty? + @input << @tail + entities.unshift @input.join + @input.clear + @tail = entities.pop + end + + entities + end + + # Flush the contents of the input buffer, i.e. return the input buffer even though + # a token has not yet been encountered + def flush + @input << @tail + buffer = @input.join + @input.clear + @tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7 + buffer + end +end diff --git a/.gems/gems/buftok-0.2.0/test/test_buftok.rb b/.gems/gems/buftok-0.2.0/test/test_buftok.rb new file mode 100644 index 0000000..535b362 --- /dev/null +++ b/.gems/gems/buftok-0.2.0/test/test_buftok.rb @@ -0,0 +1,27 @@ +require 'test/unit' +require 'buftok' + +class TestBuftok < Test::Unit::TestCase + def test_buftok + tokenizer = BufferedTokenizer.new + assert_equal %w[foo], tokenizer.extract("foo\nbar".freeze) + assert_equal %w[barbaz qux], tokenizer.extract("baz\nqux\nquu".freeze) + assert_equal 'quu', tokenizer.flush + assert_equal '', tokenizer.flush + end + + def test_delimiter + tokenizer = BufferedTokenizer.new('<>') + assert_equal ['', "foo\n"], tokenizer.extract("<>foo\n<>".freeze) + assert_equal %w[bar], tokenizer.extract('bar<>baz'.freeze) + assert_equal 'baz', tokenizer.flush + end + + def test_split_delimiter + tokenizer = BufferedTokenizer.new('<>'.freeze) + assert_equal [], tokenizer.extract('foo<'.freeze) + assert_equal %w[foo], tokenizer.extract('>bar<'.freeze) + assert_equal %w[barqux<>'.freeze) + assert_equal '', tokenizer.flush + end +end diff --git a/.gems/gems/equalizer-0.0.9/.gitignore b/.gems/gems/equalizer-0.0.9/.gitignore new file mode 100644 index 0000000..b98eb63 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.gitignore @@ -0,0 +1,37 @@ +## MAC OS +.DS_Store + +## TEXTMATE +*.tmproj +tmtags + +## EMACS +*~ +\#* +.\#* + +## VIM +*.swp + +## Rubinius +*.rbc +.rbx + +## PROJECT::GENERAL +*.gem +coverage +profiling +turbulence +rdoc +pkg +tmp +doc +log +.yardoc +measurements + +## BUNDLER +.bundle +Gemfile.lock + +## PROJECT::SPECIFIC diff --git a/.gems/gems/equalizer-0.0.9/.reek.yml b/.gems/gems/equalizer-0.0.9/.reek.yml new file mode 100644 index 0000000..7111519 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.reek.yml @@ -0,0 +1,106 @@ +--- +Attribute: + enabled: true + exclude: [] +BooleanParameter: + enabled: true + exclude: [] +ClassVariable: + enabled: true + exclude: [] +ControlParameter: + enabled: true + exclude: [] +DataClump: + enabled: true + exclude: [] + max_copies: 2 + min_clump_size: 2 +DuplicateMethodCall: + enabled: true + exclude: [] + max_calls: 1 + allow_calls: [] +FeatureEnvy: + enabled: true + exclude: [] +IrresponsibleModule: + enabled: true + exclude: [] +LongParameterList: + enabled: true + exclude: [] + max_params: 2 + overrides: + initialize: + max_params: 3 +LongYieldList: + enabled: true + exclude: [] + max_params: 2 +NestedIterators: + enabled: true + exclude: + - Equalizer#define_cmp_method + - Equalizer#define_hash_method + - Equalizer#define_inspect_method + max_allowed_nesting: 1 + ignore_iterators: [] +NilCheck: + enabled: true + exclude: [] +RepeatedConditional: + enabled: true + exclude: [] + max_ifs: 1 +TooManyInstanceVariables: + enabled: true + exclude: [] + max_instance_variables: 3 +TooManyMethods: + enabled: true + exclude: [] + max_methods: 10 +TooManyStatements: + enabled: true + exclude: + - each + max_statements: 5 +UncommunicativeMethodName: + enabled: true + exclude: [] + reject: + - !ruby/regexp /^[a-z]$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ + accept: [] +UncommunicativeModuleName: + enabled: true + exclude: [] + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ + accept: [] +UncommunicativeParameterName: + enabled: true + exclude: [] + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ + accept: [] +UncommunicativeVariableName: + enabled: true + exclude: [] + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ + accept: [] +UnusedParameters: + enabled: true + exclude: [] +UtilityFunction: + enabled: true + exclude: [] + max_helper_calls: 0 diff --git a/.gems/gems/equalizer-0.0.9/.rspec b/.gems/gems/equalizer-0.0.9/.rspec new file mode 100644 index 0000000..5e712d3 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.rspec @@ -0,0 +1,5 @@ +--color +--format progress +--profile +--warnings +--order random diff --git a/.gems/gems/equalizer-0.0.9/.rubocop.yml b/.gems/gems/equalizer-0.0.9/.rubocop.yml new file mode 100644 index 0000000..4935186 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.rubocop.yml @@ -0,0 +1,69 @@ +AllCops: + Includes: + - '**/*.rake' + - 'Gemfile' + - 'Rakefile' + Excludes: + - 'vendor/**' + +# Avoid parameter lists longer than five parameters. +ParameterLists: + Max: 3 + CountKeywordArgs: true + +# Avoid more than `Max` levels of nesting. +BlockNesting: + Max: 3 + +# Align with the style guide. +CollectionMethods: + PreferredMethods: + collect: 'map' + inject: 'reduce' + find: 'detect' + find_all: 'select' + +# Do not force public/protected/private keyword to be indented at the same +# level as the def keyword. My personal preference is to outdent these keywords +# because I think when scanning code it makes it easier to identify the +# sections of code and visually separate them. When the keyword is at the same +# level I think it sort of blends in with the def keywords and makes it harder +# to scan the code and see where the sections are. +AccessModifierIndentation: + Enabled: false + +# Disable documentation checking until a class needs to be documented once +Documentation: + Enabled: false + +# Do not always use &&/|| instead of and/or. +AndOr: + Enabled: false + +# Do not favor modifier if/unless usage when you have a single-line body +IfUnlessModifier: + Enabled: false + +# Allow case equality operator (in limited use within the specs) +CaseEquality: + Enabled: false + +# Constants do not always have to use SCREAMING_SNAKE_CASE +ConstantName: + Enabled: false + +# Not all trivial readers/writers can be defined with attr_* methods +TrivialAccessors: + Enabled: false + +# Allow empty lines around body +EmptyLinesAroundBody: + Enabled: false + +# Enforce Ruby 1.8-compatible hash syntax +HashSyntax: + EnforcedStyle: hash_rockets + +# Allow dots at the end of lines +DotPosition: + Enabled: false diff --git a/.gems/gems/equalizer-0.0.9/.ruby-gemset b/.gems/gems/equalizer-0.0.9/.ruby-gemset new file mode 100644 index 0000000..c013490 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.ruby-gemset @@ -0,0 +1 @@ +equalizer diff --git a/.gems/gems/equalizer-0.0.9/.travis.yml b/.gems/gems/equalizer-0.0.9/.travis.yml new file mode 100644 index 0000000..e7c6aef --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.travis.yml @@ -0,0 +1,29 @@ +language: ruby +cache: bundler +bundler_args: --without yard guard benchmarks +script: "bundle exec rake ci" +rvm: + - ree + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0.0 + - ruby-head + - rbx +matrix: + include: + - rvm: jruby-18mode + env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov + - rvm: jruby-19mode + env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov + - rvm: jruby-20mode + env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov + - rvm: jruby-head + env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov + fast_finish: true +notifications: + irc: + channels: + - irc.freenode.org#rom-rb + on_success: never + on_failure: change diff --git a/.gems/gems/equalizer-0.0.9/.yardstick.yml b/.gems/gems/equalizer-0.0.9/.yardstick.yml new file mode 100644 index 0000000..a6b63e8 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/.yardstick.yml @@ -0,0 +1,2 @@ +--- +threshold: 100 diff --git a/.gems/gems/equalizer-0.0.9/CONTRIBUTING.md b/.gems/gems/equalizer-0.0.9/CONTRIBUTING.md new file mode 100644 index 0000000..333b403 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/CONTRIBUTING.md @@ -0,0 +1,11 @@ +Contributing +------------ + +* If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin. +* Fork the project. +* Make your feature addition or bug fix. + * Follow this [style guide](https://github.com/dkubb/styleguide). +* Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered. +* Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) +* Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged. +* Send me a pull request. Bonus points for topic branches. diff --git a/.gems/gems/equalizer-0.0.9/Gemfile b/.gems/gems/equalizer-0.0.9/Gemfile new file mode 100644 index 0000000..633eefc --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/Gemfile @@ -0,0 +1,28 @@ +# encoding: utf-8 + +source 'https://rubygems.org' + +gem 'rake' + +group :test do + gem 'backports' + gem 'coveralls', :require => false + gem 'json', :platforms => [:ruby_19] + gem 'reek' + gem 'rspec', '~> 2.14' + gem 'rubocop', :platforms => [:ruby_19, :ruby_20] + gem 'simplecov', :require => false + gem 'yardstick' +end + +platforms :jruby, :ruby_18 do + gem 'mime-types', '~> 1.25' +end + +platforms :rbx do + gem 'racc' + gem 'rubinius-coverage', '~> 2.0' + gem 'rubysl', '~> 2.0' +end + +gemspec diff --git a/.gems/gems/equalizer-0.0.9/LICENSE b/.gems/gems/equalizer-0.0.9/LICENSE new file mode 100644 index 0000000..18b962b --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2009-2013 Dan Kubb +Copyright (c) 2012 Markus Schirp (packaging) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/equalizer-0.0.9/README.md b/.gems/gems/equalizer-0.0.9/README.md new file mode 100644 index 0000000..056a7fe --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/README.md @@ -0,0 +1,97 @@ +equalizer +========= + +Module to define equality, equivalence and inspection methods + +[![Gem Version](https://badge.fury.io/rb/equalizer.png)][gem] +[![Build Status](https://secure.travis-ci.org/dkubb/equalizer.png?branch=master)][travis] +[![Dependency Status](https://gemnasium.com/dkubb/equalizer.png)][gemnasium] +[![Code Climate](https://codeclimate.com/github/dkubb/equalizer.png)][codeclimate] +[![Coverage Status](https://coveralls.io/repos/dkubb/equalizer/badge.png?branch=master)][coveralls] + +[gem]: https://rubygems.org/gems/equalizer +[travis]: https://travis-ci.org/dkubb/equalizer +[gemnasium]: https://gemnasium.com/dkubb/equalizer +[codeclimate]: https://codeclimate.com/github/dkubb/equalizer +[coveralls]: https://coveralls.io/r/dkubb/equalizer + +Examples +-------- + +``` ruby +class GeoLocation + include Equalizer.new(:latitude, :longitude) + + attr_reader :latitude, :longitude + + def initialize(latitude, longitude) + @latitude, @longitude = latitude, longitude + end +end + +point_a = GeoLocation.new(1, 2) +point_b = GeoLocation.new(1, 2) +point_c = GeoLocation.new(2, 2) + +point_a.inspect # => "#" + +point_a == point_b # => true +point_a.hash == point_b.hash # => true +point_a.eql?(point_b) # => true +point_a.equal?(point_b) # => false + +point_a == point_c # => false +point_a.hash == point_c.hash # => false +point_a.eql?(point_c) # => false +point_a.equal?(point_c) # => false +``` + +Supported Ruby Versions +----------------------- + +This library aims to support and is [tested against][travis] the following Ruby +implementations: + +* Ruby 1.8.7 +* Ruby 1.9.2 +* Ruby 1.9.3 +* Ruby 2.0.0 +* [JRuby][] +* [Rubinius][] +* [Ruby Enterprise Edition][ree] + +[jruby]: http://jruby.org/ +[rubinius]: http://rubini.us/ +[ree]: http://www.rubyenterpriseedition.com/ + +If something doesn't work on one of these versions, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby versions or +implementations, however support will only be provided for the implementations +listed above. + +If you would like this library to support another Ruby version or +implementation, you may volunteer to be a maintainer. Being a maintainer +entails making sure all tests run and pass on that implementation. When +something breaks on your implementation, you will be responsible for providing +patches in a timely fashion. If critical issues for a particular implementation +exist at the time of a major release, support for that Ruby version may be +dropped. + +Credits +------- + +* Dan Kubb ([dkubb](https://github.com/dkubb)) +* Piotr Solnica ([solnic](https://github.com/solnic)) +* Markus Schirp ([mbj](https://github.com/mbj)) +* Erik Michaels-Ober ([sferik](https://github.com/sferik)) + +Contributing +------------- + +See [CONTRIBUTING.md](CONTRIBUTING.md) for details. + +Copyright +--------- + +Copyright © 2009-2013 Dan Kubb. See LICENSE for details. diff --git a/.gems/gems/equalizer-0.0.9/Rakefile b/.gems/gems/equalizer-0.0.9/Rakefile new file mode 100644 index 0000000..2da2334 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/Rakefile @@ -0,0 +1,39 @@ +# encoding: utf-8 + +require 'bundler' +Bundler::GemHelper.install_tasks + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) + +task :test => :spec +task :default => :spec + +require 'reek/rake/task' +Reek::Rake::Task.new do |reek| + reek.reek_opts = '--quiet' + reek.fail_on_error = true + reek.config_files = '.reek.yml' +end + +begin + require 'rubocop/rake_task' + Rubocop::RakeTask.new +rescue LoadError + desc 'Run RuboCop' + task :rubocop do + $stderr.puts 'Rubocop is disabled' + end +end + +require 'yardstick/rake/measurement' +Yardstick::Rake::Measurement.new do |measurement| + measurement.output = 'measurement/report.txt' +end + +require 'yardstick/rake/verify' +Yardstick::Rake::Verify.new do |verify| + verify.threshold = 100 +end + +task :ci => [:spec, :rubocop, :reek, :verify_measurements] diff --git a/.gems/gems/equalizer-0.0.9/equalizer.gemspec b/.gems/gems/equalizer-0.0.9/equalizer.gemspec new file mode 100644 index 0000000..9f9933f --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/equalizer.gemspec @@ -0,0 +1,21 @@ +# encoding: utf-8 + +require File.expand_path('../lib/equalizer/version', __FILE__) + +Gem::Specification.new do |gem| + gem.name = 'equalizer' + gem.version = Equalizer::VERSION.dup + gem.authors = ['Dan Kubb', 'Markus Schirp'] + gem.email = %w[dan.kubb@gmail.com mbj@schirp-dso.com] + gem.description = 'Module to define equality, equivalence and inspection methods' + gem.summary = gem.description + gem.homepage = 'https://github.com/dkubb/equalizer' + gem.licenses = 'MIT' + + gem.require_paths = %w[lib] + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n") + gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md] + + gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5') +end diff --git a/.gems/gems/equalizer-0.0.9/lib/equalizer.rb b/.gems/gems/equalizer-0.0.9/lib/equalizer.rb new file mode 100644 index 0000000..833901b --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/lib/equalizer.rb @@ -0,0 +1,122 @@ +# encoding: utf-8 + +# Define equality, equivalence and inspection methods +class Equalizer < Module + + # Initialize an Equalizer with the given keys + # + # Will use the keys with which it is initialized to define #cmp?, + # #hash, and #inspect + # + # @param [Array] keys + # + # @return [undefined] + # + # @api private + def initialize(*keys) + @keys = keys + define_methods + freeze + end + +private + + # Hook called when module is included + # + # @param [Module] descendant + # the module or class including Equalizer + # + # @return [self] + # + # @api private + def included(descendant) + super + descendant.module_eval { include Methods } + end + + # Define the equalizer methods based on #keys + # + # @return [undefined] + # + # @api private + def define_methods + define_cmp_method + define_hash_method + define_inspect_method + end + + # Define an #cmp? method based on the instance's values identified by #keys + # + # @return [undefined] + # + # @api private + def define_cmp_method + keys = @keys + define_method(:cmp?) do |comparator, other| + keys.all? { |key| send(key).send(comparator, other.send(key)) } + end + private :cmp? + end + + # Define a #hash method based on the instance's values identified by #keys + # + # @return [undefined] + # + # @api private + def define_hash_method + keys = @keys + define_method(:hash) do || + keys.map(&method(:send)).push(self.class).hash + end + end + + # Define an inspect method that reports the values of the instance's keys + # + # @return [undefined] + # + # @api private + def define_inspect_method + keys = @keys + define_method(:inspect) do || + klass = self.class + name = klass.name || klass.inspect + "#<#{name}#{keys.map { |key| " #{key}=#{send(key).inspect}" }.join}>" + end + end + + # The comparison methods + module Methods + + # Compare the object with other object for equality + # + # @example + # object.eql?(other) # => true or false + # + # @param [Object] other + # the other object to compare with + # + # @return [Boolean] + # + # @api public + def eql?(other) + instance_of?(other.class) && cmp?(__method__, other) + end + + # Compare the object with other object for equivalency + # + # @example + # object == other # => true or false + # + # @param [Object] other + # the other object to compare with + # + # @return [Boolean] + # + # @api public + def ==(other) + other = coerce(other) if respond_to?(:coerce, true) + other.kind_of?(self.class) && cmp?(__method__, other) + end + + end # module Methods +end # class Equalizer diff --git a/.gems/gems/equalizer-0.0.9/lib/equalizer/version.rb b/.gems/gems/equalizer-0.0.9/lib/equalizer/version.rb new file mode 100644 index 0000000..1451289 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/lib/equalizer/version.rb @@ -0,0 +1,8 @@ +# encoding: utf-8 + +class Equalizer < Module + + # Gem version + VERSION = '0.0.9'.freeze + +end # class Equalizer diff --git a/.gems/gems/equalizer-0.0.9/spec/spec_helper.rb b/.gems/gems/equalizer-0.0.9/spec/spec_helper.rb new file mode 100644 index 0000000..045a60b --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/spec_helper.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 + +if ENV['COVERAGE'] == 'true' + require 'simplecov' + require 'coveralls' + + SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter, + ] + + SimpleCov.start do + command_name 'spec:unit' + + add_filter 'config' + add_filter 'spec' + add_filter 'vendor' + + minimum_coverage 100 + end +end + +require 'equalizer' + +# TODO: FIXME! +# Cache correct freezer in ice_nine before +# rspec2 infects the world... +Equalizer.new + +RSpec.configure do |config| + config.expect_with :rspec do |expect_with| + expect_with.syntax = :expect + end +end diff --git a/.gems/gems/equalizer-0.0.9/spec/support/config_alias.rb b/.gems/gems/equalizer-0.0.9/spec/support/config_alias.rb new file mode 100644 index 0000000..9c3f42f --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/support/config_alias.rb @@ -0,0 +1,5 @@ +# encoding: utf-8 + +require 'rbconfig' + +::Config = RbConfig unless defined?(::Config) diff --git a/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/included_spec.rb b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/included_spec.rb new file mode 100644 index 0000000..229da6f --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/included_spec.rb @@ -0,0 +1,40 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Equalizer, '#included' do + subject { descendant.instance_exec(object) { |mod| include mod } } + + let(:object) { described_class.new } + let(:descendant) { Class.new } + let(:superclass) { described_class.superclass } + + before do + # Prevent Module.included from being called through inheritance + described_class::Methods.stub(:included) + end + + around do |example| + # Restore included method after each example + superclass.class_eval do + alias_method :original_included, :included + example.call + undef_method :included + alias_method :included, :original_included + end + end + + it 'delegates to the superclass #included method' do + # This is the most succinct approach I could think of to test whether the + # superclass#included method is called. All of the built-in rspec helpers + # did not seem to work for this. + included = false + superclass.class_eval { define_method(:included) { |_| included = true } } + expect { subject }.to change { included }.from(false).to(true) + end + + it 'includes methods into the descendant' do + subject + expect(descendant.included_modules).to include(described_class::Methods) + end +end diff --git a/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/eql_predicate_spec.rb b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/eql_predicate_spec.rb new file mode 100644 index 0000000..774eb5f --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/eql_predicate_spec.rb @@ -0,0 +1,65 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Equalizer::Methods, '#eql?' do + subject { object.eql?(other) } + + let(:object) { described_class.new(true) } + + let(:described_class) do + Class.new do + include Equalizer::Methods + + attr_reader :boolean + + def initialize(boolean) + @boolean = boolean + end + + def cmp?(comparator, other) + boolean.send(comparator, other.boolean) + end + end + end + + context 'with the same object' do + let(:other) { object } + + it { should be(true) } + + it 'is symmetric' do + should eql(other.eql?(object)) + end + end + + context 'with an equivalent object' do + let(:other) { object.dup } + + it { should be(true) } + + it 'is symmetric' do + should eql(other.eql?(object)) + end + end + + context 'with an equivalent object of a subclass' do + let(:other) { Class.new(described_class).new(true) } + + it { should be(false) } + + it 'is symmetric' do + should eql(other.eql?(object)) + end + end + + context 'with a different object' do + let(:other) { described_class.new(false) } + + it { should be(false) } + + it 'is symmetric' do + should eql(other.eql?(object)) + end + end +end diff --git a/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/equality_operator_spec.rb b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/equality_operator_spec.rb new file mode 100644 index 0000000..e08b90c --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/methods/equality_operator_spec.rb @@ -0,0 +1,108 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Equalizer::Methods, '#==' do + subject { object == other } + + let(:object) { described_class.new(true) } + let(:described_class) { Class.new(super_class) } + + let(:super_class) do + Class.new do + include Equalizer::Methods + + attr_reader :boolean + + def initialize(boolean) + @boolean = boolean + end + + def cmp?(comparator, other) + boolean.send(comparator, other.boolean) + end + end + end + + context 'with the same object' do + let(:other) { object } + + it { should be(true) } + + it 'is symmetric' do + should eql(other == object) + end + end + + context 'with an equivalent object' do + let(:other) { object.dup } + + it { should be(true) } + + it 'is symmetric' do + should eql(other == object) + end + end + + context 'with a subclass instance having equivalent obervable state' do + let(:other) { Class.new(described_class).new(true) } + + it { should be(true) } + + it 'is not symmetric' do + # the subclass instance should maintain substitutability with the object + # (in the LSP sense) the reverse is not true. + should_not eql(other == object) + end + end + + context 'with a superclass instance having equivalent observable state' do + let(:other) { super_class.new(true) } + + it { should be(false) } + + it 'is not symmetric' do + should_not eql(other == object) + end + end + + context 'with an object of another class' do + let(:other) { Class.new.new } + + it { should be(false) } + + it 'is symmetric' do + should eql(other == object) + end + end + + context 'with an equivalent object after coercion' do + let(:other) { Object.new } + + before do + # declare a private #coerce method + described_class.class_eval do + def coerce(other) + self.class.new(!!other) + end + private :coerce + end + end + + it { should be(true) } + + it 'is not symmetric' do + should_not eql(other == object) + end + end + + context 'with a different object' do + let(:other) { described_class.new(false) } + + it { should be(false) } + + it 'is symmetric' do + should eql(other == object) + end + end +end diff --git a/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/universal_spec.rb b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/universal_spec.rb new file mode 100644 index 0000000..0549e04 --- /dev/null +++ b/.gems/gems/equalizer-0.0.9/spec/unit/equalizer/universal_spec.rb @@ -0,0 +1,159 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Equalizer, '.new' do + + let(:object) { described_class } + let(:name) { 'User' } + let(:klass) { ::Class.new } + + context 'with no keys' do + subject { object.new } + + before do + # specify the class #name method + klass.stub(:name).and_return(name) + klass.send(:include, subject) + end + + let(:instance) { klass.new } + + it { should be_instance_of(object) } + + it { should be_frozen } + + it 'defines #hash and #inspect methods dynamically' do + expect(subject.public_instance_methods(false).map(&:to_s).sort). + to eql(%w[hash inspect]) + end + + describe '#eql?' do + context 'when the objects are similar' do + let(:other) { instance.dup } + + it { expect(instance.eql?(other)).to be(true) } + end + + context 'when the objects are different' do + let(:other) { double('other') } + + it { expect(instance.eql?(other)).to be(false) } + end + end + + describe '#==' do + context 'when the objects are similar' do + let(:other) { instance.dup } + + it { expect(instance == other).to be(true) } + end + + context 'when the objects are different' do + let(:other) { double('other') } + + it { expect(instance == other).to be(false) } + end + end + + describe '#hash' do + it 'has the expected arity' do + expect(klass.instance_method(:hash).arity).to be(0) + end + + it { expect(instance.hash).to eql([klass].hash) } + end + + describe '#inspect' do + it 'has the expected arity' do + expect(klass.instance_method(:inspect).arity).to be(0) + end + + it { expect(instance.inspect).to eql('#') } + end + end + + context 'with keys' do + subject { object.new(*keys) } + + let(:keys) { [:firstname, :lastname].freeze } + let(:firstname) { 'John' } + let(:lastname) { 'Doe' } + let(:instance) { klass.new(firstname, lastname) } + + let(:klass) do + ::Class.new do + attr_reader :firstname, :lastname + private :firstname, :lastname + + def initialize(firstname, lastname) + @firstname, @lastname = firstname, lastname + end + end + end + + before do + # specify the class #inspect method + klass.stub(:name).and_return(nil) + klass.stub(:inspect).and_return(name) + klass.send(:include, subject) + end + + it { should be_instance_of(object) } + + it { should be_frozen } + + it 'defines #hash and #inspect methods dynamically' do + expect(subject.public_instance_methods(false).map(&:to_s).sort). + to eql(%w[ hash inspect ]) + end + + describe '#eql?' do + context 'when the objects are similar' do + let(:other) { instance.dup } + + it { expect(instance.eql?(other)).to be(true) } + end + + context 'when the objects are different' do + let(:other) { double('other') } + + it { expect(instance.eql?(other)).to be(false) } + end + end + + describe '#==' do + context 'when the objects are similar' do + let(:other) { instance.dup } + + it { expect(instance == other).to be(true) } + end + + context 'when the objects are different type' do + let(:other) { klass.new('Foo', 'Bar') } + + it { expect(instance == other).to be(false) } + end + + context 'when the objects are from different type' do + let(:other) { double('other') } + + it { expect(instance == other).to be(false) } + end + end + + describe '#hash' do + it 'returns the expected hash' do + expect(instance.hash). + to eql([firstname, lastname, klass].hash) + end + end + + describe '#inspect' do + it 'returns the expected string' do + expect(instance.inspect). + to eql('#') + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/.document b/.gems/gems/faraday-0.9.0/.document new file mode 100644 index 0000000..1fd4b83 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/.document @@ -0,0 +1,6 @@ +CONTRIBUTING.md +LICENSE.md +README.md +bin/* +lib/**/*.rb +test/**/*.rb diff --git a/.gems/gems/faraday-0.9.0/CHANGELOG.md b/.gems/gems/faraday-0.9.0/CHANGELOG.md new file mode 100644 index 0000000..650ba1d --- /dev/null +++ b/.gems/gems/faraday-0.9.0/CHANGELOG.md @@ -0,0 +1,15 @@ +# Faraday Changelog + +## v0.9.0 + +* Add HTTPClient adapter (@hakanensari) +* Improve Retry handler (@mislav) +* Remove autoloading by default (@technoweenie) +* Improve internal docs (@technoweenie, @mislav) +* Respect user/password in http proxy string (@mislav) +* Adapter options are structs. Reinforces consistent options across adapters + (@technoweenie) +* Stop stripping trailing / off base URLs in a Faraday::Connection. (@technoweenie) +* Add a configurable URI parser. (@technoweenie) +* Remove need to manually autoload when using the authorization header helpers on `Faraday::Connection`. (@technoweenie) +* `Faraday::Adapter::Test` respects the `Faraday::RequestOptions#params_encoder` option. (@technoweenie) diff --git a/.gems/gems/faraday-0.9.0/CONTRIBUTING.md b/.gems/gems/faraday-0.9.0/CONTRIBUTING.md new file mode 100644 index 0000000..63bdf29 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/CONTRIBUTING.md @@ -0,0 +1,36 @@ +## Contributing + +You can run the test suite against a live server by running `script/test`. It +automatically starts a test server in background. Only tests in +`test/adapters/*_test.rb` require a server, though. + +``` sh +# run the whole suite +$ script/test + +# run only specific files +$ script/test excon typhoeus + +# run tests using SSL +$ SSL=yes script/test +``` + +We will accept middleware that: + +1. is useful to a broader audience, but can be implemented relatively + simple; and +2. which isn't already present in [faraday_middleware][] project. + +We will accept adapters that: + +1. support SSL & streaming; +1. are proven and may have better performance than existing ones; or +2. if they have features not present in included adapters. + +We are pushing towards a 1.0 release, when we will have to follow [Semantic +Versioning][semver]. If your patch includes changes to break compatiblitity, +note that so we can add it to the [Changelog][]. + +[semver]: http://semver.org/ +[changelog]: https://github.com/technoweenie/faraday/wiki/Changelog +[faraday_middleware]: https://github.com/pengwynn/faraday_middleware/wiki diff --git a/.gems/gems/faraday-0.9.0/Gemfile b/.gems/gems/faraday-0.9.0/Gemfile new file mode 100644 index 0000000..8824eb6 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/Gemfile @@ -0,0 +1,29 @@ +source 'https://rubygems.org' + +gem 'ffi-ncurses', '~> 0.3', :platforms => :jruby +gem 'jruby-openssl', '~> 0.8.8', :platforms => :jruby +gem 'rake' + +group :test do + gem 'coveralls', :require => false + gem 'em-http-request', '>= 1.1', :require => 'em-http' + gem 'em-synchrony', '>= 1.0.3', :require => ['em-synchrony', 'em-synchrony/em-http'] + gem 'excon', '>= 0.27.4' + gem 'httpclient', '>= 2.2' + gem 'leftright', '>= 0.9', :require => false + gem 'mime-types', '~> 1.25', :platforms => [:jruby, :ruby_18] + gem 'minitest', '>= 5.0.5' + gem 'net-http-persistent', '>= 2.5', :require => false + gem 'patron', '>= 0.4.2', :platforms => :ruby + gem 'rack-test', '>= 0.6', :require => 'rack/test' + gem 'simplecov' + gem 'sinatra', '~> 1.3' + gem 'typhoeus', '~> 0.3.3', :platforms => :ruby +end + +platforms :rbx do + gem 'rubinius-coverage' + gem 'rubysl' +end + +gemspec diff --git a/.gems/gems/faraday-0.9.0/LICENSE.md b/.gems/gems/faraday-0.9.0/LICENSE.md new file mode 100644 index 0000000..98dc016 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2009-2013 Rick Olson, Zack Hobson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/faraday-0.9.0/README.md b/.gems/gems/faraday-0.9.0/README.md new file mode 100644 index 0000000..e59d09d --- /dev/null +++ b/.gems/gems/faraday-0.9.0/README.md @@ -0,0 +1,216 @@ +# Faraday + +Faraday is an HTTP client lib that provides a common interface over many +adapters (such as Net::HTTP) and embraces the concept of Rack middleware when +processing the request/response cycle. + +Faraday supports these adapters: + +* Net::HTTP +* [Excon][] +* [Typhoeus][] +* [Patron][] +* [EventMachine][] + +It also includes a Rack adapter for hitting loaded Rack applications through +Rack::Test, and a Test adapter for stubbing requests by hand. + +## Usage + +```ruby +conn = Faraday.new(:url => 'http://sushi.com') do |faraday| + faraday.request :url_encoded # form-encode POST params + faraday.response :logger # log requests to STDOUT + faraday.adapter Faraday.default_adapter # make requests with Net::HTTP +end + +## GET ## + +response = conn.get '/nigiri/sake.json' # GET http://sushi.com/nigiri/sake.json +response.body + +conn.get '/nigiri', { :name => 'Maguro' } # GET /nigiri?name=Maguro + +conn.get do |req| # GET http://sushi.com/search?page=2&limit=100 + req.url '/search', :page => 2 + req.params['limit'] = 100 +end + +## POST ## + +conn.post '/nigiri', { :name => 'Maguro' } # POST "name=maguro" to http://sushi.com/nigiri + +# post payload as JSON instead of "www-form-urlencoded" encoding: +conn.post do |req| + req.url '/nigiri' + req.headers['Content-Type'] = 'application/json' + req.body = '{ "name": "Unagi" }' +end + +## Per-request options ## + +conn.get do |req| + req.url '/search' + req.options.timeout = 5 # open/read timeout in seconds + req.options.open_timeout = 2 # connection open timeout in seconds +end +``` + +If you don't need to set up anything, you can roll with just the bare minimum: + +```ruby +# using the default stack: +response = Faraday.get 'http://sushi.com/nigiri/sake.json' +``` + +## Advanced middleware usage + +The order in which middleware is stacked is important. Like with Rack, the +first middleware on the list wraps all others, while the last middleware is the +innermost one, so that must be the adapter. + +```ruby +Faraday.new(...) do |conn| + # POST/PUT params encoders: + conn.request :multipart + conn.request :url_encoded + + conn.adapter :net_http +end +``` + +This request middleware setup affects POST/PUT requests in the following way: + +1. `Request::Multipart` checks for files in the payload, otherwise leaves + everything untouched; +2. `Request::UrlEncoded` encodes as "application/x-www-form-urlencoded" if not + already encoded or of another type + +Swapping middleware means giving the other priority. Specifying the +"Content-Type" for the request is explicitly stating which middleware should +process it. + +Examples: + +```ruby +# uploading a file: +payload[:profile_pic] = Faraday::UploadIO.new('/path/to/avatar.jpg', 'image/jpeg') + +# "Multipart" middleware detects files and encodes with "multipart/form-data": +conn.put '/profile', payload +``` + +## Writing middleware + +Middleware are classes that implement a `call` instance method. They hook into +the request/response cycle. + +```ruby +def call(env) + # do something with the request + + @app.call(env).on_complete do + # do something with the response + end +end +``` + +It's important to do all processing of the response only in the `on_complete` +block. This enables middleware to work in parallel mode where requests are +asynchronous. + +The `env` is a hash with symbol keys that contains info about the request and, +later, response. Some keys are: + +``` +# request phase +:method - :get, :post, ... +:url - URI for the current request; also contains GET parameters +:body - POST parameters for :post/:put requests +:request_headers + +# response phase +:status - HTTP response status code, such as 200 +:body - the response body +:response_headers +``` + +## Using Faraday for testing + +```ruby +# It's possible to define stubbed request outside a test adapter block. +stubs = Faraday::Adapter::Test::Stubs.new do |stub| + stub.get('/tamago') { [200, {}, 'egg'] } +end + +# You can pass stubbed request to the test adapter or define them in a block +# or a combination of the two. +test = Faraday.new do |builder| + builder.adapter :test, stubs do |stub| + stub.get('/ebi') {[ 200, {}, 'shrimp' ]} + end +end + +# It's also possible to stub additional requests after the connection has +# been initialized. This is useful for testing. +stubs.get('/uni') {[ 200, {}, 'urchin' ]} + +resp = test.get '/tamago' +resp.body # => 'egg' +resp = test.get '/ebi' +resp.body # => 'shrimp' +resp = test.get '/uni' +resp.body # => 'urchin' +resp = test.get '/else' #=> raises "no such stub" error + +# If you like, you can treat your stubs as mocks by verifying that all of +# the stubbed calls were made. NOTE that this feature is still fairly +# experimental: It will not verify the order or count of any stub, only that +# it was called once during the course of the test. +stubs.verify_stubbed_calls +``` + +## TODO + +* support streaming requests/responses +* better stubbing API + +## Supported Ruby versions + +This library aims to support and is [tested against][travis] the following Ruby +implementations: + +* MRI 1.8.7 +* MRI 1.9.2 +* MRI 1.9.3 +* MRI 2.0.0 +* MRI 2.1.0 +* [JRuby][] +* [Rubinius][] + +If something doesn't work on one of these Ruby versions, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby +implementations, however support will only be provided for the versions listed +above. + +If you would like this library to support another Ruby version, you may +volunteer to be a maintainer. Being a maintainer entails making sure all tests +run and pass on that implementation. When something breaks on your +implementation, you will be responsible for providing patches in a timely +fashion. If critical issues for a particular implementation exist at the time +of a major release, support for that Ruby version may be dropped. + +## Copyright + +Copyright (c) 2009-2013 [Rick Olson](mailto:technoweenie@gmail.com), Zack Hobson. +See [LICENSE][] for details. + +[travis]: http://travis-ci.org/lostisland/faraday +[excon]: https://github.com/geemus/excon#readme +[typhoeus]: https://github.com/typhoeus/typhoeus#readme +[patron]: http://toland.github.com/patron/ +[eventmachine]: https://github.com/igrigorik/em-http-request#readme +[jruby]: http://jruby.org/ +[rubinius]: http://rubini.us/ +[license]: LICENSE.md diff --git a/.gems/gems/faraday-0.9.0/Rakefile b/.gems/gems/faraday-0.9.0/Rakefile new file mode 100644 index 0000000..bcd8c01 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/Rakefile @@ -0,0 +1,71 @@ +require 'date' +require 'fileutils' +require 'openssl' +require 'rake/testtask' +require 'bundler' +Bundler::GemHelper.install_tasks + +task :default => :test + +## helper functions + +def name + @name ||= Dir['*.gemspec'].first.split('.').first +end + +def version + line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/] + line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1] +end + +def gemspec_file + "#{name}.gemspec" +end + +def gem_file + "#{name}-#{version}.gem" +end + +def replace_header(head, header_name) + head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"} +end + +# Adapted from WEBrick::Utils. Skips cert extensions so it +# can be used as a CA bundle +def create_self_signed_cert(bits, cn, comment) + rsa = OpenSSL::PKey::RSA.new(bits) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + name = OpenSSL::X509::Name.new(cn) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + (365*24*60*60) + cert.public_key = rsa.public_key + cert.sign(rsa, OpenSSL::Digest::SHA1.new) + return [cert, rsa] +end + +## standard tasks + +desc "Run all tests" +task :test do + exec 'script/test' +end + +desc "Generate certificates for SSL tests" +task :'test:generate_certs' do + cert, key = create_self_signed_cert(1024, [['CN', 'localhost']], 'Faraday Test CA') + FileUtils.mkdir_p 'tmp' + File.open('tmp/faraday-cert.key', 'w') {|f| f.puts(key) } + File.open('tmp/faraday-cert.crt', 'w') {|f| f.puts(cert.to_s) } +end + +file 'tmp/faraday-cert.key' => :'test:generate_certs' +file 'tmp/faraday-cert.crt' => :'test:generate_certs' + +desc "Open an irb session preloaded with this library" +task :console do + sh "irb -rubygems -r ./lib/#{name}.rb" +end diff --git a/.gems/gems/faraday-0.9.0/faraday.gemspec b/.gems/gems/faraday-0.9.0/faraday.gemspec new file mode 100644 index 0000000..3ee9cae --- /dev/null +++ b/.gems/gems/faraday-0.9.0/faraday.gemspec @@ -0,0 +1,34 @@ +lib = "faraday" +lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__) +File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/ +version = $1 + +Gem::Specification.new do |spec| + spec.specification_version = 2 if spec.respond_to? :specification_version= + spec.required_rubygems_version = '>= 1.3.5' + + spec.name = lib + spec.version = version + + spec.summary = "HTTP/REST API client library." + + spec.authors = ["Rick Olson"] + spec.email = 'technoweenie@gmail.com' + spec.homepage = 'https://github.com/lostisland/faraday' + spec.licenses = ['MIT'] + + spec.add_dependency 'multipart-post', '>= 1.2', '< 3' + spec.add_development_dependency 'bundler', '~> 1.0' + + spec.files = %w(.document CHANGELOG.md CONTRIBUTING.md Gemfile LICENSE.md README.md Rakefile) + spec.files << "#{lib}.gemspec" + spec.files += Dir.glob("lib/**/*.rb") + spec.files += Dir.glob("test/**/*.{rb,txt}") + spec.files += Dir.glob("script/*") + + dev_null = File.exist?('/dev/null') ? '/dev/null' : 'NUL' + git_files = `git ls-files -z 2>#{dev_null}` + spec.files &= git_files.split("\0") if $?.success? + + spec.test_files = Dir.glob("test/**/*.rb") +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday.rb b/.gems/gems/faraday-0.9.0/lib/faraday.rb new file mode 100644 index 0000000..4b63ec8 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday.rb @@ -0,0 +1,268 @@ +require 'thread' +require 'cgi' +require 'set' +require 'forwardable' + +# Public: This is the main namespace for Faraday. You can either use it to +# create Faraday::Connection objects, or access it directly. +# +# Examples +# +# Faraday.get "http://faraday.com" +# +# conn = Faraday.new "http://faraday.com" +# conn.get '/' +# +module Faraday + VERSION = "0.9.0" + + class << self + # Public: Gets or sets the root path that Faraday is being loaded from. + # This is the root from where the libraries are auto-loaded from. + attr_accessor :root_path + + # Public: Gets or sets the path that the Faraday libs are loaded from. + attr_accessor :lib_path + + # Public: Gets or sets the Symbol key identifying a default Adapter to use + # for the default Faraday::Connection. + attr_reader :default_adapter + + # Public: Sets the default Faraday::Connection for simple scripts that + # access the Faraday constant directly. + # + # Faraday.get "https://faraday.com" + attr_writer :default_connection + + # Public: Sets the default options used when calling Faraday#new. + attr_writer :default_connection_options + + # Public: Initializes a new Faraday::Connection. + # + # url - The optional String base URL to use as a prefix for all + # requests. Can also be the options Hash. + # options - The optional Hash used to configure this Faraday::Connection. + # Any of these values will be set on every request made, unless + # overridden for a specific request. + # :url - String base URL. + # :params - Hash of URI query unencoded key/value pairs. + # :headers - Hash of unencoded HTTP header key/value pairs. + # :request - Hash of request options. + # :ssl - Hash of SSL options. + # :proxy - Hash of Proxy options. + # + # Examples + # + # Faraday.new 'http://faraday.com' + # + # # http://faraday.com?page=1 + # Faraday.new 'http://faraday.com', :params => {:page => 1} + # + # # same + # + # Faraday.new :url => 'http://faraday.com', + # :params => {:page => 1} + # + # Returns a Faraday::Connection. + def new(url = nil, options = nil) + block = block_given? ? Proc.new : nil + options = options ? default_connection_options.merge(options) : default_connection_options.dup + Faraday::Connection.new(url, options, &block) + end + + # Internal: Requires internal Faraday libraries. + # + # *libs - One or more relative String names to Faraday classes. + # + # Returns nothing. + def require_libs(*libs) + libs.each do |lib| + require "#{lib_path}/#{lib}" + end + end + + # Public: Updates default adapter while resetting + # #default_connection. + # + # Returns the new default_adapter. + def default_adapter=(adapter) + @default_connection = nil + @default_adapter = adapter + end + + alias require_lib require_libs + + private + # Internal: Proxies method calls on the Faraday constant to + # #default_connection. + def method_missing(name, *args, &block) + default_connection.send(name, *args, &block) + end + end + + self.root_path = File.expand_path "..", __FILE__ + self.lib_path = File.expand_path "../faraday", __FILE__ + self.default_adapter = :net_http + + # Gets the default connection used for simple scripts. + # + # Returns a Faraday::Connection, configured with the #default_adapter. + def self.default_connection + @default_connection ||= Connection.new + end + + # Gets the default connection options used when calling Faraday#new. + # + # Returns a Faraday::ConnectionOptions. + def self.default_connection_options + @default_connection_options ||= ConnectionOptions.new + end + + if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9' + begin + require 'system_timer' + Timer = SystemTimer + rescue LoadError + warn "Faraday: you may want to install system_timer for reliable timeouts" + end + end + + unless const_defined? :Timer + require 'timeout' + Timer = Timeout + end + + # Public: Adds the ability for other modules to register and lookup + # middleware classes. + module MiddlewareRegistry + # Public: Register middleware class(es) on the current module. + # + # mapping - A Hash mapping Symbol keys to classes. Classes can be expressed + # as fully qualified constant, or a Proc that will be lazily + # called to return the former. + # + # Examples + # + # module Faraday + # class Whatever + # # Middleware looked up by :foo returns Faraday::Whatever::Foo. + # register_middleware :foo => Foo + # + # # Middleware looked up by :bar returns Faraday::Whatever.const_get(:Bar) + # register_middleware :bar => :Bar + # + # # Middleware looked up by :baz requires 'baz' and returns Faraday::Whatever.const_get(:Baz) + # register_middleware :baz => [:Baz, 'baz'] + # end + # end + # + # Returns nothing. + def register_middleware(autoload_path = nil, mapping = nil) + if mapping.nil? + mapping = autoload_path + autoload_path = nil + end + middleware_mutex do + @middleware_autoload_path = autoload_path if autoload_path + (@registered_middleware ||= {}).update(mapping) + end + end + + # Public: Lookup middleware class with a registered Symbol shortcut. + # + # key - The Symbol key for the registered middleware. + # + # Examples + # + # module Faraday + # class Whatever + # register_middleware :foo => Foo + # end + # end + # + # Faraday::Whatever.lookup_middleware(:foo) + # # => Faraday::Whatever::Foo + # + # Returns a middleware Class. + def lookup_middleware(key) + load_middleware(key) || + raise(Faraday::Error.new("#{key.inspect} is not registered on #{self}")) + end + + def middleware_mutex(&block) + @middleware_mutex ||= begin + require 'monitor' + Monitor.new + end + @middleware_mutex.synchronize(&block) + end + + def fetch_middleware(key) + defined?(@registered_middleware) && @registered_middleware[key] + end + + def load_middleware(key) + value = fetch_middleware(key) + case value + when Module + value + when Symbol, String + middleware_mutex do + @registered_middleware[key] = const_get(value) + end + when Proc + middleware_mutex do + @registered_middleware[key] = value.call + end + when Array + middleware_mutex do + const, path = value + if root = @middleware_autoload_path + path = "#{root}/#{path}" + end + require(path) + @registered_middleware[key] = const + end + load_middleware(key) + end + end + end + + def self.const_missing(name) + if name.to_sym == :Builder + warn "Faraday::Builder is now Faraday::RackBuilder." + const_set name, RackBuilder + else + super + end + end + + require_libs "utils", "options", "connection", "rack_builder", "parameters", + "middleware", "adapter", "request", "response", "upload_io", "error" + + if !ENV["FARADAY_NO_AUTOLOAD"] + require_lib 'autoload' + end +end + +# not pulling in active-support JUST for this method. And I love this method. +class Object + # The primary purpose of this method is to "tap into" a method chain, + # in order to perform operations on intermediate results within the chain. + # + # Examples + # + # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a. + # tap { |x| puts "array: #{x.inspect}" }. + # select { |x| x%2 == 0 }. + # tap { |x| puts "evens: #{x.inspect}" }. + # map { |x| x*x }. + # tap { |x| puts "squares: #{x.inspect}" } + # + # Yields self. + # Returns self. + def tap + yield(self) + self + end unless Object.respond_to?(:tap) +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter.rb new file mode 100644 index 0000000..f018b50 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter.rb @@ -0,0 +1,46 @@ +module Faraday + # Public: This is a base class for all Faraday adapters. Adapters are + # responsible for fulfilling a Faraday request. + class Adapter < Middleware + CONTENT_LENGTH = 'Content-Length'.freeze + + register_middleware File.expand_path('../adapter', __FILE__), + :test => [:Test, 'test'], + :net_http => [:NetHttp, 'net_http'], + :net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'], + :typhoeus => [:Typhoeus, 'typhoeus'], + :patron => [:Patron, 'patron'], + :em_synchrony => [:EMSynchrony, 'em_synchrony'], + :em_http => [:EMHttp, 'em_http'], + :excon => [:Excon, 'excon'], + :rack => [:Rack, 'rack'], + :httpclient => [:HTTPClient, 'httpclient'] + + # Public: This module marks an Adapter as supporting parallel requests. + module Parallelism + attr_writer :supports_parallel + def supports_parallel?() @supports_parallel end + + def inherited(subclass) + super + subclass.supports_parallel = self.supports_parallel? + end + end + + extend Parallelism + self.supports_parallel = false + + def call(env) + env.clear_body if env.needs_body? + end + + def save_response(env, status, body, headers = nil) + env.status = status + env.body = body + env.response_headers = Utils::Headers.new.tap do |response_headers| + response_headers.update headers unless headers.nil? + yield(response_headers) if block_given? + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http.rb new file mode 100644 index 0000000..a248fcf --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http.rb @@ -0,0 +1,237 @@ +module Faraday + class Adapter + # EventMachine adapter is useful for either asynchronous requests + # when in EM reactor loop or for making parallel requests in + # synchronous code. + class EMHttp < Faraday::Adapter + module Options + def connection_config(env) + options = {} + configure_proxy(options, env) + configure_timeout(options, env) + configure_socket(options, env) + configure_ssl(options, env) + options + end + + def request_config(env) + options = { + :body => read_body(env), + :head => env[:request_headers], + # :keepalive => true, + # :file => 'path/to/file', # stream data off disk + } + configure_compression(options, env) + options + end + + def read_body(env) + body = env[:body] + body.respond_to?(:read) ? body.read : body + end + + def configure_proxy(options, env) + if proxy = request_options(env)[:proxy] + options[:proxy] = { + :host => proxy[:uri].host, + :port => proxy[:uri].port, + :authorization => [proxy[:user], proxy[:password]] + } + end + end + + def configure_socket(options, env) + if bind = request_options(env)[:bind] + options[:bind] = { + :host => bind[:host], + :port => bind[:port] + } + end + end + + def configure_ssl(options, env) + if env[:url].scheme == 'https' && env[:ssl] + options[:ssl] = { + :cert_chain_file => env[:ssl][:ca_file], + :verify_peer => env[:ssl].fetch(:verify, true) + } + end + end + + def configure_timeout(options, env) + timeout, open_timeout = request_options(env).values_at(:timeout, :open_timeout) + options[:connect_timeout] = options[:inactivity_timeout] = timeout + options[:connect_timeout] = open_timeout if open_timeout + end + + def configure_compression(options, env) + if env[:method] == :get and not options[:head].key? 'accept-encoding' + options[:head]['accept-encoding'] = 'gzip, compressed' + end + end + + def request_options(env) + env[:request] + end + end + + include Options + + dependency 'em-http' + + self.supports_parallel = true + + def self.setup_parallel_manager(options = nil) + Manager.new + end + + def call(env) + super + perform_request env + @app.call env + end + + def perform_request(env) + if parallel?(env) + manager = env[:parallel_manager] + manager.add { + perform_single_request(env). + callback { env[:response].finish(env) } + } + else + unless EventMachine.reactor_running? + error = nil + # start EM, block until request is completed + EventMachine.run do + perform_single_request(env). + callback { EventMachine.stop }. + errback { |client| + error = error_message(client) + EventMachine.stop + } + end + raise_error(error) if error + else + # EM is running: instruct upstream that this is an async request + env[:parallel_manager] = true + perform_single_request(env). + callback { env[:response].finish(env) }. + errback { + # TODO: no way to communicate the error in async mode + raise NotImplementedError + } + end + end + rescue EventMachine::Connectify::CONNECTError => err + if err.message.include?("Proxy Authentication Required") + raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "} + else + raise Error::ConnectionFailed, err + end + rescue => err + if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err + raise Faraday::SSLError, err + else + raise + end + end + + # TODO: reuse the connection to support pipelining + def perform_single_request(env) + req = EventMachine::HttpRequest.new(env[:url], connection_config(env)) + req.setup_request(env[:method], request_config(env)).callback { |client| + save_response(env, client.response_header.status, client.response) do |resp_headers| + client.response_header.each do |name, value| + resp_headers[name.to_sym] = value + end + end + } + end + + def error_message(client) + client.error or "request failed" + end + + def raise_error(msg) + errklass = Faraday::Error::ClientError + if msg == Errno::ETIMEDOUT + errklass = Faraday::Error::TimeoutError + msg = "request timed out" + elsif msg == Errno::ECONNREFUSED + errklass = Faraday::Error::ConnectionFailed + msg = "connection refused" + elsif msg == "connection closed by server" + errklass = Faraday::Error::ConnectionFailed + end + raise errklass, msg + end + + def parallel?(env) + !!env[:parallel_manager] + end + + # The parallel manager is designed to start an EventMachine loop + # and block until all registered requests have been completed. + class Manager + def initialize + reset + end + + def reset + @registered_procs = [] + @num_registered = 0 + @num_succeeded = 0 + @errors = [] + @running = false + end + + def running?() @running end + + def add + if running? + perform_request { yield } + else + @registered_procs << Proc.new + end + @num_registered += 1 + end + + def run + if @num_registered > 0 + @running = true + EventMachine.run do + @registered_procs.each do |proc| + perform_request(&proc) + end + end + if @errors.size > 0 + raise Faraday::Error::ClientError, @errors.first || "connection failed" + end + end + ensure + reset + end + + def perform_request + client = yield + client.callback { @num_succeeded += 1; check_finished } + client.errback { @errors << client.error; check_finished } + end + + def check_finished + if @num_succeeded + @errors.size == @num_registered + EventMachine.stop + end + end + end + end + end +end + +begin + require 'openssl' +rescue LoadError + warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support" +else + require 'faraday/adapter/em_http_ssl_patch' +end if Faraday::Adapter::EMHttp.loaded? diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http_ssl_patch.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http_ssl_patch.rb new file mode 100644 index 0000000..8bbfcbc --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_http_ssl_patch.rb @@ -0,0 +1,56 @@ +require 'openssl' +require 'em-http' + +module EmHttpSslPatch + def ssl_verify_peer(cert_string) + cert = nil + begin + cert = OpenSSL::X509::Certificate.new(cert_string) + rescue OpenSSL::X509::CertificateError + return false + end + + @last_seen_cert = cert + + if certificate_store.verify(@last_seen_cert) + begin + certificate_store.add_cert(@last_seen_cert) + rescue OpenSSL::X509::StoreError => e + raise e unless e.message == 'cert already in hash table' + end + true + else + raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}")) + end + end + + def ssl_handshake_completed + return true unless verify_peer? + + unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host) + raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate)) + else + true + end + end + + def verify_peer? + parent.connopts.tls[:verify_peer] + end + + def host + parent.connopts.host + end + + def certificate_store + @certificate_store ||= begin + store = OpenSSL::X509::Store.new + store.set_default_paths + ca_file = parent.connopts.tls[:cert_chain_file] + store.add_file(ca_file) if ca_file + store + end + end +end + +EventMachine::HttpStubConnection.send(:include, EmHttpSslPatch) diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony.rb new file mode 100644 index 0000000..305e702 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony.rb @@ -0,0 +1,92 @@ +require 'uri' + +module Faraday + class Adapter + class EMSynchrony < Faraday::Adapter + include EMHttp::Options + + dependency do + require 'em-synchrony/em-http' + require 'em-synchrony/em-multi' + require 'fiber' + end + + self.supports_parallel = true + + def self.setup_parallel_manager(options = {}) + ParallelManager.new + end + + def call(env) + super + request = EventMachine::HttpRequest.new(Utils::URI(env[:url].to_s), connection_config(env)) + + http_method = env[:method].to_s.downcase.to_sym + + # Queue requests for parallel execution. + if env[:parallel_manager] + env[:parallel_manager].add(request, http_method, request_config(env)) do |resp| + save_response(env, resp.response_header.status, resp.response) do |resp_headers| + resp.response_header.each do |name, value| + resp_headers[name.to_sym] = value + end + end + + # Finalize the response object with values from `env`. + env[:response].finish(env) + end + + # Execute single request. + else + client = nil + block = lambda { request.send(http_method, request_config(env)) } + + if !EM.reactor_running? + EM.run do + Fiber.new { + client = block.call + EM.stop + }.resume + end + else + client = block.call + end + + raise client.error if client.error + + save_response(env, client.response_header.status, client.response) do |resp_headers| + client.response_header.each do |name, value| + resp_headers[name.to_sym] = value + end + end + end + + @app.call env + rescue Errno::ECONNREFUSED + raise Error::ConnectionFailed, $! + rescue EventMachine::Connectify::CONNECTError => err + if err.message.include?("Proxy Authentication Required") + raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "} + else + raise Error::ConnectionFailed, err + end + rescue => err + if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err + raise Faraday::SSLError, err + else + raise + end + end + end + end +end + +require 'faraday/adapter/em_synchrony/parallel_manager' + +begin + require 'openssl' +rescue LoadError + warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support" +else + require 'faraday/adapter/em_http_ssl_patch' +end if Faraday::Adapter::EMSynchrony.loaded? diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony/parallel_manager.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony/parallel_manager.rb new file mode 100644 index 0000000..12a1baf --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/em_synchrony/parallel_manager.rb @@ -0,0 +1,66 @@ +module Faraday + class Adapter + class EMSynchrony < Faraday::Adapter + class ParallelManager + + # Add requests to queue. The `request` argument should be a + # `EM::HttpRequest` object. + def add(request, method, *args, &block) + queue << { + :request => request, + :method => method, + :args => args, + :block => block + } + end + + # Run all requests on queue with `EM::Synchrony::Multi`, wrapping + # it in a reactor and fiber if needed. + def run + result = nil + if !EM.reactor_running? + EM.run { + Fiber.new do + result = perform + EM.stop + end.resume + } + else + result = perform + end + result + end + + + private + + # The request queue. + def queue + @queue ||= [] + end + + # Main `EM::Synchrony::Multi` performer. + def perform + multi = ::EM::Synchrony::Multi.new + + queue.each do |item| + method = "a#{item[:method]}".to_sym + + req = item[:request].send(method, *item[:args]) + req.callback(&item[:block]) + + req_name = "req_#{multi.requests.size}".to_sym + multi.add(req_name, req) + end + + # Clear the queue, so parallel manager objects can be reused. + @queue = [] + + # Block fiber until all requests have returned. + multi.perform + end + + end # ParallelManager + end # EMSynchrony + end # Adapter +end # Faraday diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/excon.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/excon.rb new file mode 100644 index 0000000..db0c7c3 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/excon.rb @@ -0,0 +1,80 @@ +module Faraday + class Adapter + class Excon < Faraday::Adapter + dependency 'excon' + + def initialize(app, connection_options = {}) + @connection_options = connection_options + super(app) + end + + def call(env) + super + + opts = {} + if env[:url].scheme == 'https' && ssl = env[:ssl] + opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true) + opts[:ssl_ca_path] = ssl[:ca_path] if ssl[:ca_path] + opts[:ssl_ca_file] = ssl[:ca_file] if ssl[:ca_file] + opts[:client_cert] = ssl[:client_cert] if ssl[:client_cert] + opts[:client_key] = ssl[:client_key] if ssl[:client_key] + opts[:certificate] = ssl[:certificate] if ssl[:certificate] + opts[:private_key] = ssl[:private_key] if ssl[:private_key] + + # https://github.com/geemus/excon/issues/106 + # https://github.com/jruby/jruby-ossl/issues/19 + opts[:nonblock] = false + end + + if ( req = env[:request] ) + if req[:timeout] + opts[:read_timeout] = req[:timeout] + opts[:connect_timeout] = req[:timeout] + opts[:write_timeout] = req[:timeout] + end + + if req[:open_timeout] + opts[:connect_timeout] = req[:open_timeout] + opts[:write_timeout] = req[:open_timeout] + end + + if req[:proxy] + opts[:proxy] = { + :host => req[:proxy][:uri].host, + :port => req[:proxy][:uri].port, + :scheme => req[:proxy][:uri].scheme, + :user => req[:proxy][:user], + :password => req[:proxy][:password] + } + end + end + + conn = ::Excon.new(env[:url].to_s, opts.merge(@connection_options)) + + resp = conn.request \ + :method => env[:method].to_s.upcase, + :headers => env[:request_headers], + :body => read_body(env) + + save_response(env, resp.status.to_i, resp.body, resp.headers) + + @app.call env + rescue ::Excon::Errors::SocketError => err + if err.message =~ /\btimeout\b/ + raise Error::TimeoutError, err + elsif err.message =~ /\bcertificate\b/ + raise Faraday::SSLError, err + else + raise Error::ConnectionFailed, err + end + rescue ::Excon::Errors::Timeout => err + raise Error::TimeoutError, err + end + + # TODO: support streaming requests + def read_body(env) + env[:body].respond_to?(:read) ? env[:body].read : env[:body] + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/httpclient.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/httpclient.rb new file mode 100644 index 0000000..06c663f --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/httpclient.rb @@ -0,0 +1,106 @@ +module Faraday + class Adapter + class HTTPClient < Faraday::Adapter + dependency 'httpclient' + + def client + @client ||= ::HTTPClient.new + end + + def call(env) + super + + if req = env[:request] + if proxy = req[:proxy] + configure_proxy proxy + end + + if bind = req[:bind] + configure_socket bind + end + + configure_timeouts req + end + + if env[:url].scheme == 'https' && ssl = env[:ssl] + configure_ssl ssl + end + + # TODO Don't stream yet. + # https://github.com/nahi/httpclient/pull/90 + env[:body] = env[:body].read if env[:body].respond_to? :read + + resp = client.request env[:method], env[:url], + :body => env[:body], + :header => env[:request_headers] + + save_response env, resp.status, resp.body, resp.headers + + @app.call env + rescue ::HTTPClient::TimeoutError + raise Faraday::Error::TimeoutError, $! + rescue ::HTTPClient::BadResponseError => err + if err.message.include?('status 407') + raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "} + else + raise Faraday::Error::ClientError, $! + end + rescue Errno::ECONNREFUSED, EOFError + raise Faraday::Error::ConnectionFailed, $! + rescue => err + if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err + raise Faraday::SSLError, err + else + raise + end + end + + def configure_socket(bind) + client.socket_local.host = bind[:host] + client.socket_local.port = bind[:port] + end + + def configure_proxy(proxy) + client.proxy = proxy[:uri] + if proxy[:user] && proxy[:password] + client.set_proxy_auth proxy[:user], proxy[:password] + end + end + + def configure_ssl(ssl) + ssl_config = client.ssl_config + + ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file] + ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path] + ssl_config.cert_store = ssl[:cert_store] if ssl[:cert_store] + ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert] + ssl_config.client_key = ssl[:client_key] if ssl[:client_key] + ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth] + ssl_config.verify_mode = ssl_verify_mode(ssl) + end + + def configure_timeouts(req) + if req[:timeout] + client.connect_timeout = req[:timeout] + client.receive_timeout = req[:timeout] + client.send_timeout = req[:timeout] + end + + if req[:open_timeout] + client.connect_timeout = req[:open_timeout] + client.send_timeout = req[:open_timeout] + end + end + + def ssl_verify_mode(ssl) + ssl[:verify_mode] || begin + if ssl.fetch(:verify, true) + OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + else + OpenSSL::SSL::VERIFY_NONE + end + end + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http.rb new file mode 100644 index 0000000..449388a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http.rb @@ -0,0 +1,124 @@ +begin + require 'net/https' +rescue LoadError + warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support" + require 'net/http' +end +require 'zlib' + +module Faraday + class Adapter + class NetHttp < Faraday::Adapter + NET_HTTP_EXCEPTIONS = [ + EOFError, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::EHOSTUNREACH, + Errno::EINVAL, + Errno::ENETUNREACH, + Net::HTTPBadResponse, + Net::HTTPHeaderSyntaxError, + Net::ProtocolError, + SocketError, + Zlib::GzipFile::Error, + ] + + NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL) + + def call(env) + super + http = net_http_connection(env) + configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl] + + req = env[:request] + http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout] + http.open_timeout = req[:open_timeout] if req[:open_timeout] + + begin + http_response = perform_request(http, env) + rescue *NET_HTTP_EXCEPTIONS => err + if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err + raise Faraday::SSLError, err + else + raise Error::ConnectionFailed, err + end + end + + save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers| + http_response.each_header do |key, value| + response_headers[key] = value + end + end + + @app.call env + rescue Timeout::Error => err + raise Faraday::Error::TimeoutError, err + end + + def create_request(env) + request = Net::HTTPGenericRequest.new \ + env[:method].to_s.upcase, # request method + !!env[:body], # is there request body + :head != env[:method], # is there response body + env[:url].request_uri, # request uri path + env[:request_headers] # request headers + + if env[:body].respond_to?(:read) + request.body_stream = env[:body] + else + request.body = env[:body] + end + request + end + + def perform_request(http, env) + if :get == env[:method] and !env[:body] + # prefer `get` to `request` because the former handles gzip (ruby 1.9) + http.get env[:url].request_uri, env[:request_headers] + else + http.request create_request(env) + end + end + + def net_http_connection(env) + if proxy = env[:request][:proxy] + Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password]) + else + Net::HTTP + end.new(env[:url].host, env[:url].port) + end + + def configure_ssl(http, ssl) + http.use_ssl = true + http.verify_mode = ssl_verify_mode(ssl) + http.cert_store = ssl_cert_store(ssl) + + http.cert = ssl[:client_cert] if ssl[:client_cert] + http.key = ssl[:client_key] if ssl[:client_key] + http.ca_file = ssl[:ca_file] if ssl[:ca_file] + http.ca_path = ssl[:ca_path] if ssl[:ca_path] + http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth] + http.ssl_version = ssl[:version] if ssl[:version] + end + + def ssl_cert_store(ssl) + return ssl[:cert_store] if ssl[:cert_store] + # Use the default cert store by default, i.e. system ca certs + cert_store = OpenSSL::X509::Store.new + cert_store.set_default_paths + cert_store + end + + def ssl_verify_mode(ssl) + ssl[:verify_mode] || begin + if ssl.fetch(:verify, true) + OpenSSL::SSL::VERIFY_PEER + else + OpenSSL::SSL::VERIFY_NONE + end + end + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http_persistent.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http_persistent.rb new file mode 100644 index 0000000..e0cc958 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/net_http_persistent.rb @@ -0,0 +1,47 @@ +# Rely on autoloading instead of explicit require; helps avoid the "already +# initialized constant" warning on Ruby 1.8.7 when NetHttp is refereced below. +# require 'faraday/adapter/net_http' + +module Faraday + class Adapter + # Experimental adapter for net-http-persistent + class NetHttpPersistent < NetHttp + dependency 'net/http/persistent' + + def net_http_connection(env) + if proxy = env[:request][:proxy] + proxy_uri = ::URI::HTTP === proxy[:uri] ? proxy[:uri].dup : ::URI.parse(proxy[:uri].to_s) + proxy_uri.user = proxy_uri.password = nil + # awful patch for net-http-persistent 2.8 not unescaping user/password + (class << proxy_uri; self; end).class_eval do + define_method(:user) { proxy[:user] } + define_method(:password) { proxy[:password] } + end if proxy[:user] + end + Net::HTTP::Persistent.new 'Faraday', proxy_uri + end + + def perform_request(http, env) + http.request env[:url], create_request(env) + rescue Net::HTTP::Persistent::Error => error + if error.message.include? 'Timeout' + raise Faraday::Error::TimeoutError, error + elsif error.message.include? 'connection refused' + raise Faraday::Error::ConnectionFailed, error + else + raise + end + end + + def configure_ssl(http, ssl) + http.verify_mode = ssl_verify_mode(ssl) + http.cert_store = ssl_cert_store(ssl) + + http.certificate = ssl[:client_cert] if ssl[:client_cert] + http.private_key = ssl[:client_key] if ssl[:client_key] + http.ca_file = ssl[:ca_file] if ssl[:ca_file] + http.ssl_version = ssl[:version] if ssl[:version] + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/patron.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/patron.rb new file mode 100644 index 0000000..24a6d83 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/patron.rb @@ -0,0 +1,68 @@ +module Faraday + class Adapter + class Patron < Faraday::Adapter + dependency 'patron' + + def initialize(app, &block) + super(app) + @block = block + end + + def call(env) + super + + # TODO: support streaming requests + env[:body] = env[:body].read if env[:body].respond_to? :read + + session = @session ||= create_session + + if req = env[:request] + session.timeout = session.connect_timeout = req[:timeout] if req[:timeout] + session.connect_timeout = req[:open_timeout] if req[:open_timeout] + + if proxy = req[:proxy] + proxy_uri = proxy[:uri].dup + proxy_uri.user = proxy[:user] && Utils.escape(proxy[:user]).gsub('+', '%20') + proxy_uri.password = proxy[:password] && Utils.escape(proxy[:password]).gsub('+', '%20') + session.proxy = proxy_uri.to_s + end + end + + response = begin + data = env[:body] ? env[:body].to_s : nil + session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data) + rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed + raise Error::ConnectionFailed, $! + end + + save_response(env, response.status, response.body, response.headers) + + @app.call env + rescue ::Patron::TimeoutError => err + raise Faraday::Error::TimeoutError, err + rescue ::Patron::Error => err + if err.message.include?("code 407") + raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "} + else + raise Error::ConnectionFailed, err + end + end + + if loaded? && defined?(::Patron::Request::VALID_ACTIONS) + # HAX: helps but doesn't work completely + # https://github.com/toland/patron/issues/34 + ::Patron::Request::VALID_ACTIONS.tap do |actions| + actions << :patch unless actions.include? :patch + actions << :options unless actions.include? :options + end + end + + def create_session + session = ::Patron::Session.new + session.insecure = true + @block.call(session) if @block + session + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/rack.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/rack.rb new file mode 100644 index 0000000..0d21464 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/rack.rb @@ -0,0 +1,58 @@ +module Faraday + class Adapter + # Sends requests to a Rack app. + # + # Examples + # + # class MyRackApp + # def call(env) + # [200, {'Content-Type' => 'text/html'}, ["hello world"]] + # end + # end + # + # Faraday.new do |conn| + # conn.adapter :rack, MyRackApp.new + # end + class Rack < Faraday::Adapter + dependency 'rack/test' + + # not prefixed with "HTTP_" + SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ] + + def initialize(faraday_app, rack_app) + super(faraday_app) + mock_session = ::Rack::MockSession.new(rack_app) + @session = ::Rack::Test::Session.new(mock_session) + end + + def call(env) + super + rack_env = { + :method => env[:method], + :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body], + 'rack.url_scheme' => env[:url].scheme + } + + env[:request_headers].each do |name, value| + name = name.upcase.tr('-', '_') + name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name + rack_env[name] = value + end if env[:request_headers] + + timeout = env[:request][:timeout] || env[:request][:open_timeout] + response = if timeout + Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) } + else + execute_request(env, rack_env) + end + + save_response(env, response.status, response.body, response.headers) + @app.call env + end + + def execute_request(env, rack_env) + @session.request(env[:url].to_s, rack_env) + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/test.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/test.rb new file mode 100644 index 0000000..9a34575 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/test.rb @@ -0,0 +1,162 @@ +module Faraday + class Adapter + # test = Faraday::Connection.new do + # use Faraday::Adapter::Test do |stub| + # stub.get '/nigiri/sake.json' do + # [200, {}, 'hi world'] + # end + # end + # end + # + # resp = test.get '/nigiri/sake.json' + # resp.body # => 'hi world' + # + class Test < Faraday::Adapter + attr_accessor :stubs + + class Stubs + class NotFound < StandardError + end + + def initialize + # {:get => [Stub, Stub]} + @stack, @consumed = {}, {} + yield(self) if block_given? + end + + def empty? + @stack.empty? + end + + def match(request_method, path, headers, body) + return false if !@stack.key?(request_method) + stack = @stack[request_method] + consumed = (@consumed[request_method] ||= []) + + if stub = matches?(stack, path, headers, body) + consumed << stack.delete(stub) + stub + else + matches?(consumed, path, headers, body) + end + end + + def get(path, headers = {}, &block) + new_stub(:get, path, headers, &block) + end + + def head(path, headers = {}, &block) + new_stub(:head, path, headers, &block) + end + + def post(path, body=nil, headers = {}, &block) + new_stub(:post, path, headers, body, &block) + end + + def put(path, body=nil, headers = {}, &block) + new_stub(:put, path, headers, body, &block) + end + + def patch(path, body=nil, headers = {}, &block) + new_stub(:patch, path, headers, body, &block) + end + + def delete(path, headers = {}, &block) + new_stub(:delete, path, headers, &block) + end + + def options(path, headers = {}, &block) + new_stub(:options, path, headers, &block) + end + + # Raises an error if any of the stubbed calls have not been made. + def verify_stubbed_calls + failed_stubs = [] + @stack.each do |method, stubs| + unless stubs.size == 0 + failed_stubs.concat(stubs.map {|stub| + "Expected #{method} #{stub}." + }) + end + end + raise failed_stubs.join(" ") unless failed_stubs.size == 0 + end + + protected + + def new_stub(request_method, path, headers = {}, body=nil, &block) + normalized_path = Faraday::Utils.normalize_path(path) + (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block) + end + + def matches?(stack, path, headers, body) + stack.detect { |stub| stub.matches?(path, headers, body) } + end + end + + class Stub < Struct.new(:path, :params, :headers, :body, :block) + def initialize(full, headers, body, block) + path, query = full.split('?') + params = query ? + Faraday::Utils.parse_nested_query(query) : + {} + super(path, params, headers, body, block) + end + + def matches?(request_uri, request_headers, request_body) + request_path, request_query = request_uri.split('?') + request_params = request_query ? + Faraday::Utils.parse_nested_query(request_query) : + {} + request_path == path && + params_match?(request_params) && + (body.to_s.size.zero? || request_body == body) && + headers_match?(request_headers) + end + + def params_match?(request_params) + params.keys.all? do |key| + request_params[key] == params[key] + end + end + + def headers_match?(request_headers) + headers.keys.all? do |key| + request_headers[key] == headers[key] + end + end + + def to_s + "#{path} #{body}" + end + end + + def initialize(app, stubs=nil, &block) + super(app) + @stubs = stubs || Stubs.new + configure(&block) if block + end + + def configure + yield(stubs) + end + + def call(env) + super + normalized_path = Faraday::Utils.normalize_path(env[:url]) + params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder + + if stub = stubs.match(env[:method], normalized_path, env.request_headers, env[:body]) + env[:params] = (query = env[:url].query) ? + params_encoder.decode(query) : + {} + status, headers, body = stub.block.call(env) + save_response(env, status, body, headers) + else + raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}" + end + @app.call(env) + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/adapter/typhoeus.rb b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/typhoeus.rb new file mode 100644 index 0000000..69b6a51 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/adapter/typhoeus.rb @@ -0,0 +1,123 @@ +module Faraday + class Adapter + class Typhoeus < Faraday::Adapter + self.supports_parallel = true + + def self.setup_parallel_manager(options = {}) + options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options) + end + + dependency 'typhoeus' + + def call(env) + super + perform_request env + @app.call env + end + + def perform_request(env) + read_body env + + hydra = env[:parallel_manager] || self.class.setup_parallel_manager + hydra.queue request(env) + hydra.run unless parallel?(env) + rescue Errno::ECONNREFUSED + raise Error::ConnectionFailed, $! + end + + # TODO: support streaming requests + def read_body(env) + env[:body] = env[:body].read if env[:body].respond_to? :read + end + + def request(env) + method = env[:method] + # For some reason, prevents Typhoeus from using "100-continue". + # We want this because Webrick 1.3.1 can't seem to handle it w/ PUT. + method = method.to_s.upcase if method == :put + + req = ::Typhoeus::Request.new env[:url].to_s, + :method => method, + :body => env[:body], + :headers => env[:request_headers], + :disable_ssl_peer_verification => (env[:ssl] && env[:ssl].disable?) + + configure_ssl req, env + configure_proxy req, env + configure_timeout req, env + configure_socket req, env + + req.on_complete do |resp| + if resp.timed_out? + if parallel?(env) + # TODO: error callback in async mode + else + raise Faraday::Error::TimeoutError, "request timed out" + end + end + + case resp.curl_return_code + when 0 + # everything OK + when 7 + raise Error::ConnectionFailed, resp.curl_error_message + when 60 + raise Faraday::SSLError, resp.curl_error_message + else + raise Error::ClientError, resp.curl_error_message + end + + save_response(env, resp.code, resp.body) do |response_headers| + response_headers.parse resp.headers + end + # in async mode, :response is initialized at this point + env[:response].finish(env) if parallel?(env) + end + + req + end + + def configure_ssl(req, env) + ssl = env[:ssl] + + req.ssl_version = ssl[:version] if ssl[:version] + req.ssl_cert = ssl[:client_cert] if ssl[:client_cert] + req.ssl_key = ssl[:client_key] if ssl[:client_key] + req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file] + req.ssl_capath = ssl[:ca_path] if ssl[:ca_path] + end + + def configure_proxy(req, env) + proxy = request_options(env)[:proxy] + return unless proxy + + req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}" + + if proxy[:user] && proxy[:password] + req.proxy_username = proxy[:user] + req.proxy_password = proxy[:password] + end + end + + def configure_timeout(req, env) + env_req = request_options(env) + req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout] + req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout] + end + + def configure_socket(req, env) + if bind = request_options(env)[:bind] + req.interface = bind[:host] + end + end + + def request_options(env) + env[:request] + end + + def parallel?(env) + !!env[:parallel_manager] + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/autoload.rb b/.gems/gems/faraday-0.9.0/lib/faraday/autoload.rb new file mode 100644 index 0000000..ec413ff --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/autoload.rb @@ -0,0 +1,85 @@ +module Faraday + # Internal: Adds the ability for other modules to manage autoloadable + # constants. + module AutoloadHelper + # Internal: Registers the constants to be auto loaded. + # + # prefix - The String require prefix. If the path is inside Faraday, then + # it will be prefixed with the root path of this loaded Faraday + # version. + # options - Hash of Symbol => String library names. + # + # Examples. + # + # Faraday.autoload_all 'faraday/foo', + # :Bar => 'bar' + # + # # requires faraday/foo/bar to load Faraday::Bar. + # Faraday::Bar + # + # + # Returns nothing. + def autoload_all(prefix, options) + if prefix =~ /^faraday(\/|$)/i + prefix = File.join(Faraday.root_path, prefix) + end + options.each do |const_name, path| + autoload const_name, File.join(prefix, path) + end + end + + # Internal: Loads each autoloaded constant. If thread safety is a concern, + # wrap this in a Mutex. + # + # Returns nothing. + def load_autoloaded_constants + constants.each do |const| + const_get(const) if autoload?(const) + end + end + + # Internal: Filters the module's contents with those that have been already + # autoloaded. + # + # Returns an Array of Class/Module objects. + def all_loaded_constants + constants.map { |c| const_get(c) }. + select { |a| a.respond_to?(:loaded?) && a.loaded? } + end + end + + class Adapter + extend AutoloadHelper + autoload_all 'faraday/adapter', + :NetHttp => 'net_http', + :NetHttpPersistent => 'net_http_persistent', + :Typhoeus => 'typhoeus', + :EMSynchrony => 'em_synchrony', + :EMHttp => 'em_http', + :Patron => 'patron', + :Excon => 'excon', + :Test => 'test', + :Rack => 'rack', + :HTTPClient => 'httpclient' + end + + class Request + extend AutoloadHelper + autoload_all 'faraday/request', + :UrlEncoded => 'url_encoded', + :Multipart => 'multipart', + :Retry => 'retry', + :Timeout => 'timeout', + :Authorization => 'authorization', + :BasicAuthentication => 'basic_authentication', + :TokenAuthentication => 'token_authentication', + :Instrumentation => 'instrumentation' + end + + class Response + extend AutoloadHelper + autoload_all 'faraday/response', + :RaiseError => 'raise_error', + :Logger => 'logger' + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/connection.rb b/.gems/gems/faraday-0.9.0/lib/faraday/connection.rb new file mode 100644 index 0000000..1e408e2 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/connection.rb @@ -0,0 +1,432 @@ +module Faraday + # Public: Connection objects manage the default properties and the middleware + # stack for fulfilling an HTTP request. + # + # Examples + # + # conn = Faraday::Connection.new 'http://sushi.com' + # + # # GET http://sushi.com/nigiri + # conn.get 'nigiri' + # # => # + # + class Connection + # A Set of allowed HTTP verbs. + METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options] + + # Public: Returns a Hash of URI query unencoded key/value pairs. + attr_reader :params + + # Public: Returns a Hash of unencoded HTTP header key/value pairs. + attr_reader :headers + + # Public: Returns a URI with the prefix used for all requests from this + # Connection. This includes a default host name, scheme, port, and path. + attr_reader :url_prefix + + # Public: Returns the Faraday::Builder for this Connection. + attr_reader :builder + + # Public: Returns a Hash of the request options. + attr_reader :options + + # Public: Returns a Hash of the SSL options. + attr_reader :ssl + + # Public: Returns the parallel manager for this Connection. + attr_reader :parallel_manager + + # Public: Sets the default parallel manager for this connection. + attr_writer :default_parallel_manager + + # Public: Initializes a new Faraday::Connection. + # + # url - URI or String base URL to use as a prefix for all + # requests (optional). + # options - Hash or Faraday::ConnectionOptions. + # :url - URI or String base URL (default: "http:/"). + # :params - Hash of URI query unencoded key/value pairs. + # :headers - Hash of unencoded HTTP header key/value pairs. + # :request - Hash of request options. + # :ssl - Hash of SSL options. + # :proxy - URI, String or Hash of HTTP proxy options + # (default: "http_proxy" environment variable). + # :uri - URI or String + # :user - String (optional) + # :password - String (optional) + def initialize(url = nil, options = nil) + if url.is_a?(Hash) + options = ConnectionOptions.from(url) + url = options.url + else + options = ConnectionOptions.from(options) + end + + @parallel_manager = nil + @headers = Utils::Headers.new + @params = Utils::ParamsHash.new + @options = options.request + @ssl = options.ssl + @default_parallel_manager = options.parallel_manager + + @builder = options.builder || begin + # pass an empty block to Builder so it doesn't assume default middleware + options.new_builder(block_given? ? Proc.new { |b| } : nil) + end + + self.url_prefix = url || 'http:/' + + @params.update(options.params) if options.params + @headers.update(options.headers) if options.headers + + @proxy = nil + proxy(options.fetch(:proxy) { + uri = ENV['http_proxy'] + if uri && !uri.empty? + uri = 'http://' + uri if uri !~ /^http/i + uri + end + }) + + yield(self) if block_given? + + @headers[:user_agent] ||= "Faraday v#{VERSION}" + end + + # Public: Sets the Hash of URI query unencoded key/value pairs. + def params=(hash) + @params.replace hash + end + + # Public: Sets the Hash of unencoded HTTP header key/value pairs. + def headers=(hash) + @headers.replace hash + end + + extend Forwardable + + def_delegators :builder, :build, :use, :request, :response, :adapter, :app + + # Public: Makes an HTTP request without a body. + # + # url - The optional String base URL to use as a prefix for all + # requests. Can also be the options Hash. + # params - Hash of URI query unencoded key/value pairs. + # headers - Hash of unencoded HTTP header key/value pairs. + # + # Examples + # + # conn.get '/items', {:page => 1}, :accept => 'application/json' + # conn.head '/items/1' + # + # # ElasticSearch example sending a body with GET. + # conn.get '/twitter/tweet/_search' do |req| + # req.headers[:content_type] = 'application/json' + # req.params[:routing] = 'kimchy' + # req.body = JSON.generate(:query => {...}) + # end + # + # Yields a Faraday::Response for further request customizations. + # Returns a Faraday::Response. + # + # Signature + # + # (url = nil, params = nil, headers = nil) + # + # verb - An HTTP verb: get, head, or delete. + %w[get head delete].each do |method| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(url = nil, params = nil, headers = nil) + run_request(:#{method}, url, nil, headers) { |request| + request.params.update(params) if params + yield(request) if block_given? + } + end + RUBY + end + + # Public: Makes an HTTP request with a body. + # + # url - The optional String base URL to use as a prefix for all + # requests. Can also be the options Hash. + # body - The String body for the request. + # headers - Hash of unencoded HTTP header key/value pairs. + # + # Examples + # + # conn.post '/items', data, :content_type => 'application/json' + # + # # Simple ElasticSearch indexing sample. + # conn.post '/twitter/tweet' do |req| + # req.headers[:content_type] = 'application/json' + # req.params[:routing] = 'kimchy' + # req.body = JSON.generate(:user => 'kimchy', ...) + # end + # + # Yields a Faraday::Response for further request customizations. + # Returns a Faraday::Response. + # + # Signature + # + # (url = nil, body = nil, headers = nil) + # + # verb - An HTTP verb: post, put, or patch. + %w[post put patch].each do |method| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(url = nil, body = nil, headers = nil, &block) + run_request(:#{method}, url, body, headers, &block) + end + RUBY + end + + # Public: Sets up the Authorization header with these credentials, encoded + # with base64. + # + # login - The authentication login. + # pass - The authentication password. + # + # Examples + # + # conn.basic_auth 'Aladdin', 'open sesame' + # conn.headers['Authorization'] + # # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + # + # Returns nothing. + def basic_auth(login, pass) + set_authorization_header(:basic_auth, login, pass) + end + + # Public: Sets up the Authorization header with the given token. + # + # token - The String token. + # options - Optional Hash of extra token options. + # + # Examples + # + # conn.token_auth 'abcdef', :foo => 'bar' + # conn.headers['Authorization'] + # # => "Token token=\"abcdef\", + # foo=\"bar\"" + # + # Returns nothing. + def token_auth(token, options = nil) + set_authorization_header(:token_auth, token, options) + end + + # Public: Sets up a custom Authorization header. + # + # type - The String authorization type. + # token - The String or Hash token. A String value is taken literally, and + # a Hash is encoded into comma separated key/value pairs. + # + # Examples + # + # conn.authorization :Bearer, 'mF_9.B5f-4.1JqM' + # conn.headers['Authorization'] + # # => "Bearer mF_9.B5f-4.1JqM" + # + # conn.authorization :Token, :token => 'abcdef', :foo => 'bar' + # conn.headers['Authorization'] + # # => "Token token=\"abcdef\", + # foo=\"bar\"" + # + # Returns nothing. + def authorization(type, token) + set_authorization_header(:authorization, type, token) + end + + # Internal: Traverse the middleware stack in search of a + # parallel-capable adapter. + # + # Yields in case of not found. + # + # Returns a parallel manager or nil if not found. + def default_parallel_manager + @default_parallel_manager ||= begin + handler = @builder.handlers.detect do |h| + h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel? + end + + if handler + handler.klass.setup_parallel_manager + elsif block_given? + yield + end + end + end + + # Public: Determine if this Faraday::Connection can make parallel requests. + # + # Returns true or false. + def in_parallel? + !!@parallel_manager + end + + # Public: Sets up the parallel manager to make a set of requests. + # + # manager - The parallel manager that this Connection's Adapter uses. + # + # Yields a block to execute multiple requests. + # Returns nothing. + def in_parallel(manager = nil) + @parallel_manager = manager || default_parallel_manager { + warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack" + warn caller[2,10].join("\n") + nil + } + yield + @parallel_manager && @parallel_manager.run + ensure + @parallel_manager = nil + end + + # Public: Gets or Sets the Hash proxy options. + def proxy(arg = nil) + return @proxy if arg.nil? + @proxy = ProxyOptions.from(arg) + end + + def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port= + def_delegator :url_prefix, :path, :path_prefix + + # Public: Parses the giving url with URI and stores the individual + # components in this connection. These components serve as defaults for + # requests made by this connection. + # + # url - A String or URI. + # + # Examples + # + # conn = Faraday::Connection.new { ... } + # conn.url_prefix = "https://sushi.com/api" + # conn.scheme # => https + # conn.path_prefix # => "/api" + # + # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri + # + # Returns the parsed URI from teh given input.. + def url_prefix=(url, encoder = nil) + uri = @url_prefix = Utils.URI(url) + self.path_prefix = uri.path + + params.merge_query(uri.query, encoder) + uri.query = nil + + with_uri_credentials(uri) do |user, password| + basic_auth user, password + uri.user = uri.password = nil + end + + uri + end + + # Public: Sets the path prefix and ensures that it always has a leading + # slash. + # + # value - A String. + # + # Returns the new String path prefix. + def path_prefix=(value) + url_prefix.path = if value + value = '/' + value unless value[0,1] == '/' + value + end + end + + # Public: Takes a relative url for a request and combines it with the defaults + # set on the connection instance. + # + # conn = Faraday::Connection.new { ... } + # conn.url_prefix = "https://sushi.com/api?token=abc" + # conn.scheme # => https + # conn.path_prefix # => "/api" + # + # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2 + # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2 + # + def build_url(url = nil, extra_params = nil) + uri = build_exclusive_url(url) + + query_values = params.dup.merge_query(uri.query, options.params_encoder) + query_values.update extra_params if extra_params + uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder) + + uri + end + + # Builds and runs the Faraday::Request. + # + # method - The Symbol HTTP method. + # url - The String or URI to access. + # body - The String body + # headers - Hash of unencoded HTTP header key/value pairs. + # + # Returns a Faraday::Response. + def run_request(method, url, body, headers) + if !METHODS.include?(method) + raise ArgumentError, "unknown http method: #{method}" + end + + request = build_request(method) do |req| + req.url(url) if url + req.headers.update(headers) if headers + req.body = body if body + yield(req) if block_given? + end + + builder.build_response(self, request) + end + + # Creates and configures the request object. + # + # Returns the new Request. + def build_request(method) + Request.create(method) do |req| + req.params = self.params.dup + req.headers = self.headers.dup + req.options = self.options.merge(:proxy => self.proxy) + yield(req) if block_given? + end + end + + # Internal: Build an absolute URL based on url_prefix. + # + # url - A String or URI-like object + # params - A Faraday::Utils::ParamsHash to replace the query values + # of the resulting url (default: nil). + # + # Returns the resulting URI instance. + def build_exclusive_url(url = nil, params = nil) + url = nil if url.respond_to?(:empty?) and url.empty? + base = url_prefix + if url and base.path and base.path !~ /\/$/ + base = base.dup + base.path = base.path + '/' # ensure trailing slash + end + uri = url ? base + url : base + uri.query = params.to_query(options.params_encoder) if params + uri.query = nil if uri.query and uri.query.empty? + uri + end + + # Internal: Creates a duplicate of this Faraday::Connection. + # + # Returns a Faraday::Connection. + def dup + self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup) + end + + # Internal: Yields username and password extracted from a URI if they both exist. + def with_uri_credentials(uri) + if uri.user and uri.password + yield(Utils.unescape(uri.user), Utils.unescape(uri.password)) + end + end + + def set_authorization_header(header_type, *args) + header = Faraday::Request.lookup_middleware(header_type). + header(*args) + headers[Faraday::Request::Authorization::KEY] = header + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/error.rb b/.gems/gems/faraday-0.9.0/lib/faraday/error.rb new file mode 100644 index 0000000..1771230 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/error.rb @@ -0,0 +1,53 @@ +module Faraday + class Error < StandardError; end + class MissingDependency < Error; end + + class ClientError < Error + attr_reader :response + + def initialize(ex, response = nil) + @wrapped_exception = nil + @response = response + + if ex.respond_to?(:backtrace) + super(ex.message) + @wrapped_exception = ex + elsif ex.respond_to?(:each_key) + super("the server responded with status #{ex[:status]}") + @response = ex + else + super(ex.to_s) + end + end + + def backtrace + if @wrapped_exception + @wrapped_exception.backtrace + else + super + end + end + + def inspect + %(#<#{self.class}>) + end + end + + class ConnectionFailed < ClientError; end + class ResourceNotFound < ClientError; end + class ParsingError < ClientError; end + + class TimeoutError < ClientError + def initialize(ex = nil) + super(ex || "timeout") + end + end + + class SSLError < ClientError + end + + [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound, + :ParsingError, :TimeoutError, :SSLError].each do |const| + Error.const_set(const, Faraday.const_get(const)) + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/middleware.rb b/.gems/gems/faraday-0.9.0/lib/faraday/middleware.rb new file mode 100644 index 0000000..c45d51a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/middleware.rb @@ -0,0 +1,37 @@ +module Faraday + class Middleware + extend MiddlewareRegistry + + class << self + attr_accessor :load_error + private :load_error= + end + + self.load_error = nil + + # Executes a block which should try to require and reference dependent libraries + def self.dependency(lib = nil) + lib ? require(lib) : yield + rescue LoadError, NameError => error + self.load_error = error + end + + def self.new(*) + raise "missing dependency for #{self}: #{load_error.message}" unless loaded? + super + end + + def self.loaded? + load_error.nil? + end + + def self.inherited(subclass) + super + subclass.send(:load_error=, self.load_error) + end + + def initialize(app = nil) + @app = app + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/options.rb b/.gems/gems/faraday-0.9.0/lib/faraday/options.rb new file mode 100644 index 0000000..c1b36f6 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/options.rb @@ -0,0 +1,350 @@ +module Faraday + # Subclasses Struct with some special helpers for converting from a Hash to + # a Struct. + class Options < Struct + # Public + def self.from(value) + value ? new.update(value) : new + end + + # Public + def each + return to_enum(:each) unless block_given? + members.each do |key| + yield(key.to_sym, send(key)) + end + end + + # Public + def update(obj) + obj.each do |key, value| + if sub_options = self.class.options_for(key) + value = sub_options.from(value) if value + elsif Hash === value + hash = {} + value.each do |hash_key, hash_value| + hash[hash_key] = hash_value + end + value = hash + end + + self.send("#{key}=", value) unless value.nil? + end + self + end + + alias merge! update + + # Public + def delete(key) + value = send(key) + send("#{key}=", nil) + value + end + + # Public + def clear + members.each { |member| delete(member) } + end + + # Public + def merge(value) + dup.update(value) + end + + # Public + def fetch(key, *args) + unless symbolized_key_set.include?(key.to_sym) + key_setter = "#{key}=" + if args.size > 0 + send(key_setter, args.first) + elsif block_given? + send(key_setter, Proc.new.call(key)) + else + raise self.class.fetch_error_class, "key not found: #{key.inspect}" + end + end + send(key) + end + + # Public + def values_at(*keys) + keys.map { |key| send(key) } + end + + # Public + def keys + members.reject { |member| send(member).nil? } + end + + # Public + def empty? + keys.empty? + end + + # Public + def each_key + return to_enum(:each_key) unless block_given? + keys.each do |key| + yield(key) + end + end + + # Public + def key?(key) + keys.include?(key) + end + + alias has_key? key? + + # Public + def each_value + return to_enum(:each_value) unless block_given? + values.each do |value| + yield(value) + end + end + + # Public + def value?(value) + values.include?(value) + end + + alias has_value? value? + + # Public + def to_hash + hash = {} + members.each do |key| + value = send(key) + hash[key.to_sym] = value unless value.nil? + end + hash + end + + # Internal + def inspect + values = [] + members.each do |member| + value = send(member) + values << "#{member}=#{value.inspect}" if value + end + values = values.empty? ? ' (empty)' : (' ' << values.join(", ")) + + %(#<#{self.class}#{values}>) + end + + # Internal + def self.options(mapping) + attribute_options.update(mapping) + end + + # Internal + def self.options_for(key) + attribute_options[key] + end + + # Internal + def self.attribute_options + @attribute_options ||= {} + end + + def self.memoized(key) + memoized_attributes[key.to_sym] = Proc.new + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{key}() self[:#{key}]; end + RUBY + end + + def self.memoized_attributes + @memoized_attributes ||= {} + end + + def [](key) + key = key.to_sym + if method = self.class.memoized_attributes[key] + super(key) || (self[key] = instance_eval(&method)) + else + super + end + end + + def symbolized_key_set + @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym }) + end + + def self.inherited(subclass) + super + subclass.attribute_options.update(attribute_options) + subclass.memoized_attributes.update(memoized_attributes) + end + + def self.fetch_error_class + @fetch_error_class ||= if Object.const_defined?(:KeyError) + ::KeyError + else + ::IndexError + end + end + end + + class RequestOptions < Options.new(:params_encoder, :proxy, :bind, + :timeout, :open_timeout, :boundary, + :oauth) + + def []=(key, value) + if key && key.to_sym == :proxy + super(key, value ? ProxyOptions.from(value) : nil) + else + super(key, value) + end + end + end + + class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode, + :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version) + + def verify? + verify != false + end + + def disable? + !verify? + end + end + + class ProxyOptions < Options.new(:uri, :user, :password) + extend Forwardable + def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path= + + def self.from(value) + case value + when String + value = {:uri => Utils.URI(value)} + when URI + value = {:uri => value} + when Hash, Options + if uri = value.delete(:uri) + value[:uri] = Utils.URI(uri) + end + end + super(value) + end + + memoized(:user) { uri.user && Utils.unescape(uri.user) } + memoized(:password) { uri.password && Utils.unescape(uri.password) } + end + + class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url, + :parallel_manager, :params, :headers, :builder_class) + + options :request => RequestOptions, :ssl => SSLOptions + + memoized(:request) { self.class.options_for(:request).new } + + memoized(:ssl) { self.class.options_for(:ssl).new } + + memoized(:builder_class) { RackBuilder } + + def new_builder(block) + builder_class.new(&block) + end + end + + class Env < Options.new(:method, :body, :url, :request, :request_headers, + :ssl, :parallel_manager, :params, :response, :response_headers, :status) + + ContentLength = 'Content-Length'.freeze + StatusesWithoutBody = Set.new [204, 304] + SuccessfulStatuses = 200..299 + + # A Set of HTTP verbs that typically send a body. If no body is set for + # these requests, the Content-Length header is set to 0. + MethodsWithBodies = Set.new [:post, :put, :patch, :options] + + options :request => RequestOptions, + :request_headers => Utils::Headers, :response_headers => Utils::Headers + + extend Forwardable + + def_delegators :request, :params_encoder + + # Public + def [](key) + if in_member_set?(key) + super(key) + else + custom_members[key] + end + end + + # Public + def []=(key, value) + if in_member_set?(key) + super(key, value) + else + custom_members[key] = value + end + end + + # Public + def success? + SuccessfulStatuses.include?(status) + end + + # Public + def needs_body? + !body && MethodsWithBodies.include?(method) + end + + # Public + def clear_body + request_headers[ContentLength] = '0' + self.body = '' + end + + # Public + def parse_body? + !StatusesWithoutBody.include?(status) + end + + # Public + def parallel? + !!parallel_manager + end + + def inspect + attrs = [nil] + members.each do |mem| + if value = send(mem) + attrs << "@#{mem}=#{value.inspect}" + end + end + if !custom_members.empty? + attrs << "@custom=#{custom_members.inspect}" + end + %(#<#{self.class}#{attrs.join(" ")}>) + end + + # Internal + def custom_members + @custom_members ||= {} + end + + # Internal + if members.first.is_a?(Symbol) + def in_member_set?(key) + self.class.member_set.include?(key.to_sym) + end + else + def in_member_set?(key) + self.class.member_set.include?(key.to_s) + end + end + + # Internal + def self.member_set + @member_set ||= Set.new(members) + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/parameters.rb b/.gems/gems/faraday-0.9.0/lib/faraday/parameters.rb new file mode 100644 index 0000000..136c43b --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/parameters.rb @@ -0,0 +1,193 @@ +module Faraday + module NestedParamsEncoder + ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/ + + def self.escape(s) + return s.to_s.gsub(ESCAPE_RE) { + '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase + }.tr(' ', '+') + end + + def self.unescape(s) + CGI.unescape(s.to_s) + end + + def self.encode(params) + return nil if params == nil + + if !params.is_a?(Array) + if !params.respond_to?(:to_hash) + raise TypeError, + "Can't convert #{params.class} into Hash." + end + params = params.to_hash + params = params.map do |key, value| + key = key.to_s if key.kind_of?(Symbol) + [key, value] + end + # Useful default for OAuth and caching. + # Only to be used for non-Array inputs. Arrays should preserve order. + params.sort! + end + + # Helper lambda + to_query = lambda do |parent, value| + if value.is_a?(Hash) + value = value.map do |key, val| + key = escape(key) + [key, val] + end + value.sort! + buffer = "" + value.each do |key, val| + new_parent = "#{parent}%5B#{key}%5D" + buffer << "#{to_query.call(new_parent, val)}&" + end + return buffer.chop + elsif value.is_a?(Array) + buffer = "" + value.each_with_index do |val, i| + new_parent = "#{parent}%5B%5D" + buffer << "#{to_query.call(new_parent, val)}&" + end + return buffer.chop + else + encoded_value = escape(value) + return "#{parent}=#{encoded_value}" + end + end + + # The params have form [['key1', 'value1'], ['key2', 'value2']]. + buffer = '' + params.each do |parent, value| + encoded_parent = escape(parent) + buffer << "#{to_query.call(encoded_parent, value)}&" + end + return buffer.chop + end + + def self.decode(query) + return nil if query == nil + # Recursive helper lambda + dehash = lambda do |hash| + hash.each do |(key, value)| + if value.kind_of?(Hash) + hash[key] = dehash.call(value) + end + end + # Numeric keys implies an array + if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ } + hash.sort.inject([]) do |accu, (_, value)| + accu << value; accu + end + else + hash + end + end + + empty_accumulator = {} + return ((query.split('&').map do |pair| + pair.split('=', 2) if pair && !pair.empty? + end).compact.inject(empty_accumulator.dup) do |accu, (key, value)| + key = unescape(key) + if value.kind_of?(String) + value = unescape(value.gsub(/\+/, ' ')) + end + + array_notation = !!(key =~ /\[\]$/) + subkeys = key.split(/[\[\]]+/) + current_hash = accu + for i in 0...(subkeys.size - 1) + subkey = subkeys[i] + current_hash[subkey] = {} unless current_hash[subkey] + current_hash = current_hash[subkey] + end + if array_notation + current_hash[subkeys.last] = [] unless current_hash[subkeys.last] + current_hash[subkeys.last] << value + else + current_hash[subkeys.last] = value + end + accu + end).inject(empty_accumulator.dup) do |accu, (key, value)| + accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value + accu + end + end + end + + module FlatParamsEncoder + ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/ + + def self.escape(s) + return s.to_s.gsub(ESCAPE_RE) { + '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase + }.tr(' ', '+') + end + + def self.unescape(s) + CGI.unescape(s.to_s) + end + + def self.encode(params) + return nil if params == nil + + if !params.is_a?(Array) + if !params.respond_to?(:to_hash) + raise TypeError, + "Can't convert #{params.class} into Hash." + end + params = params.to_hash + params = params.map do |key, value| + key = key.to_s if key.kind_of?(Symbol) + [key, value] + end + # Useful default for OAuth and caching. + # Only to be used for non-Array inputs. Arrays should preserve order. + params.sort! + end + + # The params have form [['key1', 'value1'], ['key2', 'value2']]. + buffer = '' + params.each do |key, value| + encoded_key = escape(key) + value = value.to_s if value == true || value == false + if value == nil + buffer << "#{encoded_key}&" + elsif value.kind_of?(Array) + value.each do |sub_value| + encoded_value = escape(sub_value) + buffer << "#{encoded_key}=#{encoded_value}&" + end + else + encoded_value = escape(value) + buffer << "#{encoded_key}=#{encoded_value}&" + end + end + return buffer.chop + end + + def self.decode(query) + empty_accumulator = {} + return nil if query == nil + split_query = (query.split('&').map do |pair| + pair.split('=', 2) if pair && !pair.empty? + end).compact + return split_query.inject(empty_accumulator.dup) do |accu, pair| + pair[0] = unescape(pair[0]) + pair[1] = true if pair[1].nil? + if pair[1].respond_to?(:to_str) + pair[1] = unescape(pair[1].to_str.gsub(/\+/, " ")) + end + if accu[pair[0]].kind_of?(Array) + accu[pair[0]] << pair[1] + elsif accu[pair[0]] + accu[pair[0]] = [accu[pair[0]], pair[1]] + else + accu[pair[0]] = pair[1] + end + accu + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/rack_builder.rb b/.gems/gems/faraday-0.9.0/lib/faraday/rack_builder.rb new file mode 100644 index 0000000..204ce41 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/rack_builder.rb @@ -0,0 +1,212 @@ +module Faraday + # A Builder that processes requests into responses by passing through an inner + # middleware stack (heavily inspired by Rack). + # + # Faraday::Connection.new(:url => 'http://sushi.com') do |builder| + # builder.request :url_encoded # Faraday::Request::UrlEncoded + # builder.adapter :net_http # Faraday::Adapter::NetHttp + # end + class RackBuilder + attr_accessor :handlers + + # Error raised when trying to modify the stack after calling `lock!` + class StackLocked < RuntimeError; end + + # borrowed from ActiveSupport::Dependencies::Reference & + # ActionDispatch::MiddlewareStack::Middleware + class Handler + @@constants_mutex = Mutex.new + @@constants = Hash.new { |h, k| + value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k) + @@constants_mutex.synchronize { h[k] = value } + } + + attr_reader :name + + def initialize(klass, *args, &block) + @name = klass.to_s + if klass.respond_to?(:name) + @@constants_mutex.synchronize { @@constants[@name] = klass } + end + @args, @block = args, block + end + + def klass() @@constants[@name] end + def inspect() @name end + + def ==(other) + if other.is_a? Handler + self.name == other.name + elsif other.respond_to? :name + klass == other + else + @name == other.to_s + end + end + + def build(app) + klass.new(app, *@args, &@block) + end + end + + def initialize(handlers = []) + @handlers = handlers + if block_given? + build(&Proc.new) + elsif @handlers.empty? + # default stack, if nothing else is configured + self.request :url_encoded + self.adapter Faraday.default_adapter + end + end + + def build(options = {}) + raise_if_locked + @handlers.clear unless options[:keep] + yield(self) if block_given? + end + + def [](idx) + @handlers[idx] + end + + # Locks the middleware stack to ensure no further modifications are possible. + def lock! + @handlers.freeze + end + + def locked? + @handlers.frozen? + end + + def use(klass, *args, &block) + if klass.is_a? Symbol + use_symbol(Faraday::Middleware, klass, *args, &block) + else + raise_if_locked + @handlers << self.class::Handler.new(klass, *args, &block) + end + end + + def request(key, *args, &block) + use_symbol(Faraday::Request, key, *args, &block) + end + + def response(key, *args, &block) + use_symbol(Faraday::Response, key, *args, &block) + end + + def adapter(key, *args, &block) + use_symbol(Faraday::Adapter, key, *args, &block) + end + + ## methods to push onto the various positions in the stack: + + def insert(index, *args, &block) + raise_if_locked + index = assert_index(index) + handler = self.class::Handler.new(*args, &block) + @handlers.insert(index, handler) + end + + alias_method :insert_before, :insert + + def insert_after(index, *args, &block) + index = assert_index(index) + insert(index + 1, *args, &block) + end + + def swap(index, *args, &block) + raise_if_locked + index = assert_index(index) + @handlers.delete_at(index) + insert(index, *args, &block) + end + + def delete(handler) + raise_if_locked + @handlers.delete(handler) + end + + # Processes a Request into a Response by passing it through this Builder's + # middleware stack. + # + # connection - Faraday::Connection + # request - Faraday::Request + # + # Returns a Faraday::Response. + def build_response(connection, request) + app.call(build_env(connection, request)) + end + + # The "rack app" wrapped in middleware. All requests are sent here. + # + # The builder is responsible for creating the app object. After this, + # the builder gets locked to ensure no further modifications are made + # to the middleware stack. + # + # Returns an object that responds to `call` and returns a Response. + def app + @app ||= begin + lock! + to_app(lambda { |env| + response = Response.new + response.finish(env) unless env.parallel? + env.response = response + }) + end + end + + def to_app(inner_app) + # last added handler is the deepest and thus closest to the inner app + @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) } + end + + def ==(other) + other.is_a?(self.class) && @handlers == other.handlers + end + + def dup + self.class.new(@handlers.dup) + end + + # ENV Keys + # :method - a symbolized request method (:get, :post) + # :body - the request body that will eventually be converted to a string. + # :url - URI instance for the current request. + # :status - HTTP response status code + # :request_headers - hash of HTTP Headers to be sent to the server + # :response_headers - Hash of HTTP headers from the server + # :parallel_manager - sent if the connection is in parallel mode + # :request - Hash of options for configuring the request. + # :timeout - open/read timeout Integer in seconds + # :open_timeout - read timeout Integer in seconds + # :proxy - Hash of proxy options + # :uri - Proxy Server URI + # :user - Proxy server username + # :password - Proxy server password + # :ssl - Hash of options for configuring SSL requests. + def build_env(connection, request) + Env.new(request.method, request.body, + connection.build_exclusive_url(request.path, request.params), + request.options, request.headers, connection.ssl, + connection.parallel_manager) + end + + private + + def raise_if_locked + raise StackLocked, "can't modify middleware stack after making a request" if locked? + end + + def use_symbol(mod, key, *args, &block) + use(mod.lookup_middleware(key), *args, &block) + end + + def assert_index(index) + idx = index.is_a?(Integer) ? index : @handlers.index(index) + raise "No such handler: #{index.inspect}" unless idx + idx + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request.rb new file mode 100644 index 0000000..481077f --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request.rb @@ -0,0 +1,92 @@ +module Faraday + # Used to setup urls, params, headers, and the request body in a sane manner. + # + # @connection.post do |req| + # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1' + # req.headers['b'] = '2' # Header + # req.params['c'] = '3' # GET Param + # req['b'] = '2' # also Header + # req.body = 'abc' + # end + # + class Request < Struct.new(:method, :path, :params, :headers, :body, :options) + extend MiddlewareRegistry + + register_middleware File.expand_path('../request', __FILE__), + :url_encoded => [:UrlEncoded, 'url_encoded'], + :multipart => [:Multipart, 'multipart'], + :retry => [:Retry, 'retry'], + :authorization => [:Authorization, 'authorization'], + :basic_auth => [:BasicAuthentication, 'basic_authentication'], + :token_auth => [:TokenAuthentication, 'token_authentication'], + :instrumentation => [:Instrumentation, 'instrumentation'] + + def self.create(request_method) + new(request_method).tap do |request| + yield(request) if block_given? + end + end + + # Public: Replace params, preserving the existing hash type + def params=(hash) + if params + params.replace hash + else + super + end + end + + # Public: Replace request headers, preserving the existing hash type + def headers=(hash) + if headers + headers.replace hash + else + super + end + end + + def url(path, params = nil) + if path.respond_to? :query + if query = path.query + path = path.dup + path.query = nil + end + else + path, query = path.split('?', 2) + end + self.path = path + self.params.merge_query query, options.params_encoder + self.params.update(params) if params + end + + def [](key) + headers[key] + end + + def []=(key, value) + headers[key] = value + end + + # ENV Keys + # :method - a symbolized request method (:get, :post) + # :body - the request body that will eventually be converted to a string. + # :url - URI instance for the current request. + # :status - HTTP response status code + # :request_headers - hash of HTTP Headers to be sent to the server + # :response_headers - Hash of HTTP headers from the server + # :parallel_manager - sent if the connection is in parallel mode + # :request - Hash of options for configuring the request. + # :timeout - open/read timeout Integer in seconds + # :open_timeout - read timeout Integer in seconds + # :proxy - Hash of proxy options + # :uri - Proxy Server URI + # :user - Proxy server username + # :password - Proxy server password + # :ssl - Hash of options for configuring SSL requests. + def to_env(connection) + Env.new(method, body, connection.build_exclusive_url(path, params), + options, headers, connection.ssl, connection.parallel_manager) + end + end +end + diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/authorization.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/authorization.rb new file mode 100644 index 0000000..43b4528 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/authorization.rb @@ -0,0 +1,42 @@ +module Faraday + class Request::Authorization < Faraday::Middleware + KEY = "Authorization".freeze unless defined? KEY + + # Public + def self.header(type, token) + case token + when String, Symbol + "#{type} #{token}" + when Hash + build_hash(type.to_s, token) + else + raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}" + end + end + + # Internal + def self.build_hash(type, hash) + offset = KEY.size + type.size + 3 + comma = ",\n#{' ' * offset}" + values = [] + hash.each do |key, value| + values << "#{key}=#{value.to_s.inspect}" + end + "#{type} #{values * comma}" + end + + def initialize(app, type, token) + @header_value = self.class.header(type, token) + super(app) + end + + # Public + def call(env) + unless env.request_headers[KEY] + env.request_headers[KEY] = @header_value + end + @app.call(env) + end + end +end + diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/basic_authentication.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/basic_authentication.rb new file mode 100644 index 0000000..54c8dee --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/basic_authentication.rb @@ -0,0 +1,13 @@ +require 'base64' + +module Faraday + class Request::BasicAuthentication < Request.load_middleware(:authorization) + # Public + def self.header(login, pass) + value = Base64.encode64([login, pass].join(':')) + value.gsub!("\n", '') + super(:Basic, value) + end + end +end + diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/instrumentation.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/instrumentation.rb new file mode 100644 index 0000000..42af8bc --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/instrumentation.rb @@ -0,0 +1,36 @@ +module Faraday + class Request::Instrumentation < Faraday::Middleware + class Options < Faraday::Options.new(:name, :instrumenter) + def name + self[:name] ||= 'request.faraday' + end + + def instrumenter + self[:instrumenter] ||= ActiveSupport::Notifications + end + end + + # Public: Instruments requests using Active Support. + # + # Measures time spent only for synchronous requests. + # + # Examples + # + # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env| + # url = env[:url] + # http_method = env[:method].to_s.upcase + # duration = ends - starts + # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration] + # end + def initialize(app, options = nil) + super(app) + @name, @instrumenter = Options.from(options).values_at(:name, :instrumenter) + end + + def call(env) + @instrumenter.instrument(@name, env) do + @app.call(env) + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/multipart.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/multipart.rb new file mode 100644 index 0000000..38b452a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/multipart.rb @@ -0,0 +1,63 @@ +require File.expand_path("../url_encoded", __FILE__) + +module Faraday + class Request::Multipart < Request::UrlEncoded + self.mime_type = 'multipart/form-data'.freeze + DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY + + def call(env) + match_content_type(env) do |params| + env.request.boundary ||= DEFAULT_BOUNDARY + env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}" + env.body = create_multipart(env, params) + end + @app.call env + end + + def process_request?(env) + type = request_type(env) + env.body.respond_to?(:each_key) and !env.body.empty? and ( + (type.empty? and has_multipart?(env.body)) or + type == self.class.mime_type + ) + end + + def has_multipart?(obj) + # string is an enum in 1.8, returning list of itself + if obj.respond_to?(:each) && !obj.is_a?(String) + (obj.respond_to?(:values) ? obj.values : obj).each do |val| + return true if (val.respond_to?(:content_type) || has_multipart?(val)) + end + end + false + end + + def create_multipart(env, params) + boundary = env.request.boundary + parts = process_params(params) do |key, value| + Faraday::Parts::Part.new(boundary, key, value) + end + parts << Faraday::Parts::EpiloguePart.new(boundary) + + body = Faraday::CompositeReadIO.new(parts) + env.request_headers[Faraday::Env::ContentLength] = body.length.to_s + return body + end + + def process_params(params, prefix = nil, pieces = nil, &block) + params.inject(pieces || []) do |all, (key, value)| + key = "#{prefix}[#{key}]" if prefix + + case value + when Array + values = value.inject([]) { |a,v| a << [nil, v] } + process_params(values, key, all, &block) + when Hash + process_params(value, key, all, &block) + else + all << block.call(key, value) + end + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/retry.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/retry.rb new file mode 100644 index 0000000..0459b67 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/retry.rb @@ -0,0 +1,118 @@ +module Faraday + # Catches exceptions and retries each request a limited number of times. + # + # By default, it retries 2 times and handles only timeout exceptions. It can + # be configured with an arbitrary number of retries, a list of exceptions to + # handle, a retry interval, a percentage of randomness to add to the retry + # interval, and a backoff factor. + # + # Examples + # + # Faraday.new do |conn| + # conn.request :retry, max: 2, interval: 0.05, + # interval_randomness: 0.5, backoff_factor: 2 + # exceptions: [CustomException, 'Timeout::Error'] + # conn.adapter ... + # end + # + # This example will result in a first interval that is random between 0.05 and 0.075 and a second + # interval that is random between 0.1 and 0.15 + # + class Request::Retry < Faraday::Middleware + class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions) + def self.from(value) + if Fixnum === value + new(value) + else + super(value) + end + end + + def max + (self[:max] ||= 2).to_i + end + + def interval + (self[:interval] ||= 0).to_f + end + + def interval_randomness + (self[:interval_randomness] ||= 0).to_i + end + + def backoff_factor + (self[:backoff_factor] ||= 1).to_f + end + + def exceptions + Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error', + Error::TimeoutError]) + end + + end + + # Public: Initialize middleware + # + # Options: + # max - Maximum number of retries (default: 2) + # interval - Pause in seconds between retries (default: 0) + # interval_randomness - The maximum random interval amount expressed + # as a float between 0 and 1 to use in addition to the + # interval. (default: 0) + # backoff_factor - The amount to multiple each successive retry's + # interval amount by in order to provide backoff + # (default: 1) + # exceptions - The list of exceptions to handle. Exceptions can be + # given as Class, Module, or String. (default: + # [Errno::ETIMEDOUT, Timeout::Error, + # Error::TimeoutError]) + def initialize(app, options = nil) + super(app) + @options = Options.from(options) + @errmatch = build_exception_matcher(@options.exceptions) + end + + def sleep_amount(retries) + retry_index = @options.max - retries + current_interval = @options.interval * (@options.backoff_factor ** retry_index) + random_interval = rand * @options.interval_randomness.to_f * @options.interval + current_interval + random_interval + end + + def call(env) + retries = @options.max + request_body = env[:body] + begin + env[:body] = request_body # after failure env[:body] is set to the response body + @app.call(env) + rescue @errmatch + if retries > 0 + retries -= 1 + sleep sleep_amount(retries + 1) + retry + end + raise + end + end + + # Private: construct an exception matcher object. + # + # An exception matcher for the rescue clause can usually be any object that + # responds to `===`, but for Ruby 1.8 it has to be a Class or Module. + def build_exception_matcher(exceptions) + matcher = Module.new + (class << matcher; self; end).class_eval do + define_method(:===) do |error| + exceptions.any? do |ex| + if ex.is_a? Module + error.is_a? ex + else + error.class.to_s == ex.to_s + end + end + end + end + matcher + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/token_authentication.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/token_authentication.rb new file mode 100644 index 0000000..2558608 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/token_authentication.rb @@ -0,0 +1,15 @@ +module Faraday + class Request::TokenAuthentication < Request.load_middleware(:authorization) + # Public + def self.header(token, options = nil) + options ||= {} + options[:token] = token + super(:Token, options) + end + + def initialize(app, token, options = nil) + super(app, token, options) + end + end +end + diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/request/url_encoded.rb b/.gems/gems/faraday-0.9.0/lib/faraday/request/url_encoded.rb new file mode 100644 index 0000000..b02a266 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/request/url_encoded.rb @@ -0,0 +1,36 @@ +module Faraday + class Request::UrlEncoded < Faraday::Middleware + CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE + + class << self + attr_accessor :mime_type + end + self.mime_type = 'application/x-www-form-urlencoded'.freeze + + def call(env) + match_content_type(env) do |data| + params = Faraday::Utils::ParamsHash[data] + env.body = params.to_query(env.params_encoder) + end + @app.call env + end + + def match_content_type(env) + if process_request?(env) + env.request_headers[CONTENT_TYPE] ||= self.class.mime_type + yield(env.body) unless env.body.respond_to?(:to_str) + end + end + + def process_request?(env) + type = request_type(env) + env.body and (type.empty? or type == self.class.mime_type) + end + + def request_type(env) + type = env.request_headers[CONTENT_TYPE].to_s + type = type.split(';', 2).first if type.index(';') + type + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/response.rb b/.gems/gems/faraday-0.9.0/lib/faraday/response.rb new file mode 100644 index 0000000..fa55958 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/response.rb @@ -0,0 +1,93 @@ +require 'forwardable' + +module Faraday + class Response + # Used for simple response middleware. + class Middleware < Faraday::Middleware + def call(env) + @app.call(env).on_complete do |environment| + on_complete(environment) + end + end + + # Override this to modify the environment after the response has finished. + # Calls the `parse` method if defined + def on_complete(env) + env.body = parse(env.body) if respond_to?(:parse) && env.parse_body? + end + end + + extend Forwardable + extend MiddlewareRegistry + + register_middleware File.expand_path('../response', __FILE__), + :raise_error => [:RaiseError, 'raise_error'], + :logger => [:Logger, 'logger'] + + def initialize(env = nil) + @env = Env.from(env) if env + @on_complete_callbacks = [] + end + + attr_reader :env + + def_delegators :env, :to_hash + + def status + finished? ? env.status : nil + end + + def headers + finished? ? env.response_headers : {} + end + def_delegator :headers, :[] + + def body + finished? ? env.body : nil + end + + def finished? + !!env + end + + def on_complete + if not finished? + @on_complete_callbacks << Proc.new + else + yield(env) + end + return self + end + + def finish(env) + raise "response already finished" if finished? + @env = Env.from(env) + @on_complete_callbacks.each { |callback| callback.call(env) } + return self + end + + def success? + finished? && env.success? + end + + # because @on_complete_callbacks cannot be marshalled + def marshal_dump + !finished? ? nil : { + :status => @env.status, :body => @env.body, + :response_headers => @env.response_headers + } + end + + def marshal_load(env) + @env = Env.from(env) + end + + # Expand the env with more properties, without overriding existing ones. + # Useful for applying request params after restoring a marshalled Response. + def apply_request(request_env) + raise "response didn't finish yet" unless finished? + @env = Env.from(request_env).update(@env) + return self + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/response/logger.rb b/.gems/gems/faraday-0.9.0/lib/faraday/response/logger.rb new file mode 100644 index 0000000..cab7f1b --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/response/logger.rb @@ -0,0 +1,34 @@ +require 'forwardable' + +module Faraday + class Response::Logger < Response::Middleware + extend Forwardable + + def initialize(app, logger = nil) + super(app) + @logger = logger || begin + require 'logger' + ::Logger.new(STDOUT) + end + end + + def_delegators :@logger, :debug, :info, :warn, :error, :fatal + + def call(env) + info "#{env.method} #{env.url.to_s}" + debug('request') { dump_headers env.request_headers } + super + end + + def on_complete(env) + info('Status') { env.status.to_s } + debug('response') { dump_headers env.response_headers } + end + + private + + def dump_headers(headers) + headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n") + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/response/raise_error.rb b/.gems/gems/faraday-0.9.0/lib/faraday/response/raise_error.rb new file mode 100644 index 0000000..437762b --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/response/raise_error.rb @@ -0,0 +1,21 @@ +module Faraday + class Response::RaiseError < Response::Middleware + ClientErrorStatuses = 400...600 + + def on_complete(env) + case env[:status] + when 404 + raise Faraday::Error::ResourceNotFound, response_values(env) + when 407 + # mimic the behavior that we get with proxy requests with HTTPS + raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "} + when ClientErrorStatuses + raise Faraday::Error::ClientError, response_values(env) + end + end + + def response_values(env) + {:status => env.status, :headers => env.response_headers, :body => env.body} + end + end +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/upload_io.rb b/.gems/gems/faraday-0.9.0/lib/faraday/upload_io.rb new file mode 100644 index 0000000..9130d15 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/upload_io.rb @@ -0,0 +1,67 @@ +begin + require 'composite_io' + require 'parts' + require 'stringio' +rescue LoadError + $stderr.puts "Install the multipart-post gem." + raise +end + +module Faraday + # Similar but not compatible with ::CompositeReadIO provided by multipart-post. + class CompositeReadIO + def initialize(*parts) + @parts = parts.flatten + @ios = @parts.map { |part| part.to_io } + @index = 0 + end + + def length + @parts.inject(0) { |sum, part| sum + part.length } + end + + def rewind + @ios.each { |io| io.rewind } + @index = 0 + end + + # Read from IOs in order until `length` bytes have been received. + def read(length = nil, outbuf = nil) + got_result = false + outbuf = outbuf ? outbuf.replace("") : "" + + while io = current_io + if result = io.read(length) + got_result ||= !result.nil? + result.force_encoding("BINARY") if result.respond_to?(:force_encoding) + outbuf << result + length -= result.length if length + break if length == 0 + end + advance_io + end + (!got_result && length) ? nil : outbuf + end + + def close + @ios.each { |io| io.close } + end + + def ensure_open_and_readable + # Rubinius compatibility + end + + private + + def current_io + @ios[@index] + end + + def advance_io + @index += 1 + end + end + + UploadIO = ::UploadIO + Parts = ::Parts +end diff --git a/.gems/gems/faraday-0.9.0/lib/faraday/utils.rb b/.gems/gems/faraday-0.9.0/lib/faraday/utils.rb new file mode 100644 index 0000000..1cd6526 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/lib/faraday/utils.rb @@ -0,0 +1,297 @@ +require 'thread' +Faraday.require_libs 'parameters' + +module Faraday + module Utils + extend self + + # Adapted from Rack::Utils::HeaderHash + class Headers < ::Hash + def self.from(value) + new(value) + end + + def initialize(hash = nil) + super() + @names = {} + self.update(hash || {}) + end + + # need to synchronize concurrent writes to the shared KeyMap + keymap_mutex = Mutex.new + + # symbol -> string mapper + cache + KeyMap = Hash.new do |map, key| + value = if key.respond_to?(:to_str) + key + else + key.to_s.split('_'). # :user_agent => %w(user agent) + each { |w| w.capitalize! }. # => %w(User Agent) + join('-') # => "User-Agent" + end + keymap_mutex.synchronize { map[key] = value } + end + KeyMap[:etag] = "ETag" + + def [](k) + k = KeyMap[k] + super(k) || super(@names[k.downcase]) + end + + def []=(k, v) + k = KeyMap[k] + k = (@names[k.downcase] ||= k) + # join multiple values with a comma + v = v.to_ary.join(', ') if v.respond_to? :to_ary + super(k, v) + end + + def fetch(k, *args, &block) + k = KeyMap[k] + key = @names.fetch(k.downcase, k) + super(key, *args, &block) + end + + def delete(k) + k = KeyMap[k] + if k = @names[k.downcase] + @names.delete k.downcase + super(k) + end + end + + def include?(k) + @names.include? k.downcase + end + + alias_method :has_key?, :include? + alias_method :member?, :include? + alias_method :key?, :include? + + def merge!(other) + other.each { |k, v| self[k] = v } + self + end + alias_method :update, :merge! + + def merge(other) + hash = dup + hash.merge! other + end + + def replace(other) + clear + self.update other + self + end + + def to_hash() ::Hash.new.update(self) end + + def parse(header_string) + return unless header_string && !header_string.empty? + header_string.split(/\r\n/). + tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line + map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines + each { |key, value| + # join multiple values with a comma + if self[key] + self[key] << ', ' << value + else + self[key] = value + end + } + end + end + + # hash with stringified keys + class ParamsHash < Hash + def [](key) + super(convert_key(key)) + end + + def []=(key, value) + super(convert_key(key), value) + end + + def delete(key) + super(convert_key(key)) + end + + def include?(key) + super(convert_key(key)) + end + + alias_method :has_key?, :include? + alias_method :member?, :include? + alias_method :key?, :include? + + def update(params) + params.each do |key, value| + self[key] = value + end + self + end + alias_method :merge!, :update + + def merge(params) + dup.update(params) + end + + def replace(other) + clear + update(other) + end + + def merge_query(query, encoder = nil) + if query && !query.empty? + update((encoder || Utils.default_params_encoder).decode(query)) + end + self + end + + def to_query(encoder = nil) + (encoder || Utils.default_params_encoder).encode(self) + end + + private + + def convert_key(key) + key.to_s + end + end + + def build_query(params) + FlatParamsEncoder.encode(params) + end + + def build_nested_query(params) + NestedParamsEncoder.encode(params) + end + + ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/ + + def escape(s) + s.to_s.gsub(ESCAPE_RE) {|match| + '%' + match.unpack('H2' * match.bytesize).join('%').upcase + }.tr(' ', '+') + end + + def unescape(s) CGI.unescape s.to_s end + + DEFAULT_SEP = /[&;] */n + + # Adapted from Rack + def parse_query(query) + FlatParamsEncoder.decode(query) + end + + def parse_nested_query(query) + NestedParamsEncoder.decode(query) + end + + def default_params_encoder + @default_params_encoder ||= NestedParamsEncoder + end + + class << self + attr_writer :default_params_encoder + end + + # Stolen from Rack + def normalize_params(params, name, v = nil) + name =~ %r(\A[\[\]]*([^\[\]]+)\]*) + k = $1 || '' + after = $' || '' + + return if k.empty? + + if after == "" + if params[k] + params[k] = Array[params[k]] unless params[k].kind_of?(Array) + params[k] << v + else + params[k] = v + end + elsif after == "[]" + params[k] ||= [] + raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + params[k] << v + elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) + child_key = $1 + params[k] ||= [] + raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key) + normalize_params(params[k].last, child_key, v) + else + params[k] << normalize_params({}, child_key, v) + end + else + params[k] ||= {} + raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash) + params[k] = normalize_params(params[k], after, v) + end + + return params + end + + # Normalize URI() behavior across Ruby versions + # + # url - A String or URI. + # + # Returns a parsed URI. + def URI(url) + if url.respond_to?(:host) + url + elsif url.respond_to?(:to_str) + default_uri_parser.call(url) + else + raise ArgumentError, "bad argument (expected URI object or URI string)" + end + end + + def default_uri_parser + @default_uri_parser ||= begin + require 'uri' + Kernel.method(:URI) + end + end + + def default_uri_parser=(parser) + @default_uri_parser = if parser.respond_to?(:call) || parser.nil? + parser + else + parser.method(:parse) + end + end + + # Receives a String or URI and returns just the path with the query string sorted. + def normalize_path(url) + url = URI(url) + (url.path.start_with?('/') ? url.path : '/' + url.path) + + (url.query ? "?#{sort_query_params(url.query)}" : "") + end + + # Recursive hash update + def deep_merge!(target, hash) + hash.each do |key, value| + if Hash === value and Hash === target[key] + target[key] = deep_merge(target[key], value) + else + target[key] = value + end + end + target + end + + # Recursive hash merge + def deep_merge(source, hash) + deep_merge!(source.dup, hash) + end + + protected + + def sort_query_params(query) + query.split('&').sort.join('&') + end + end +end diff --git a/.gems/gems/faraday-0.9.0/script/console b/.gems/gems/faraday-0.9.0/script/console new file mode 100755 index 0000000..5d18d7a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/console @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Usage: script/console +# Starts an IRB console with this library loaded. + +gemspec="$(ls *.gemspec | head -1)" + +exec bundle exec irb -r "${gemspec%.*}" diff --git a/.gems/gems/faraday-0.9.0/script/generate_certs b/.gems/gems/faraday-0.9.0/script/generate_certs new file mode 100755 index 0000000..30063f7 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/generate_certs @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +# Usage: generate_certs +# Generate test certs for testing Faraday with SSL + +require 'openssl' +require 'fileutils' + +$shell = ARGV.include? '-s' + +# Adapted from WEBrick::Utils. Skips cert extensions so it +# can be used as a CA bundle +def create_self_signed_cert(bits, cn, comment) + rsa = OpenSSL::PKey::RSA.new(bits) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + name = OpenSSL::X509::Name.new(cn) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + (365*24*60*60) + cert.public_key = rsa.public_key + cert.sign(rsa, OpenSSL::Digest::SHA1.new) + return [cert, rsa] +end + +def write(file, contents, env_var) + FileUtils.mkdir_p(File.dirname(file)) + File.open(file, 'w') {|f| f.puts(contents) } + puts %(export #{env_var}="#{file}") if $shell +end + + +# One cert / CA for ease of testing when ignoring verification +cert, key = create_self_signed_cert(1024, [['CN', 'localhost']], 'Faraday Test CA') +write 'tmp/faraday-cert.key', key, 'SSL_KEY' +write 'tmp/faraday-cert.crt', cert, 'SSL_FILE' + +# And a second CA to prove that verification can fail +cert, key = create_self_signed_cert(1024, [['CN', 'real-ca.com']], 'A different CA') +write 'tmp/faraday-different-ca-cert.key', key, 'SSL_KEY_ALT' +write 'tmp/faraday-different-ca-cert.crt', cert, 'SSL_FILE_ALT' diff --git a/.gems/gems/faraday-0.9.0/script/package b/.gems/gems/faraday-0.9.0/script/package new file mode 100755 index 0000000..926f489 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/package @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Usage: script/gem +# Updates the gemspec and builds a new gem in the pkg directory. + +mkdir -p pkg +gem build *.gemspec +mv *.gem pkg diff --git a/.gems/gems/faraday-0.9.0/script/proxy-server b/.gems/gems/faraday-0.9.0/script/proxy-server new file mode 100755 index 0000000..e5ba4bc --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/proxy-server @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +# Usage: script/proxy-server [-p PORT] [-u USER:PASSWORD] +require 'webrick' +require 'webrick/httpproxy' + +port = 4001 + +if found = ARGV.index('-p') + port = ARGV[found + 1].to_i +end +if found = ARGV.index('-u') + username, password = ARGV[found + 1].split(':', 2) +end + +match_credentials = lambda { |credentials| + got_username, got_password = credentials.to_s.unpack("m*")[0].split(":", 2) + got_username == username && got_password == password +} + +log_io = $stdout +log_io.sync = true + +webrick_opts = { + :Port => port, :Logger => WEBrick::Log::new(log_io), + :AccessLog => [[log_io, "[%{X-Faraday-Adapter}i] %m %U -> %s %b"]], + :ProxyAuthProc => lambda { |req, res| + if username + type, credentials = req.header['proxy-authorization'].first.to_s.split(/\s+/, 2) + unless "Basic" == type && match_credentials.call(credentials) + res['proxy-authenticate'] = %{Basic realm="testing"} + raise WEBrick::HTTPStatus::ProxyAuthenticationRequired + end + end + } +} + +proxy = WEBrick::HTTPProxyServer.new(webrick_opts) + +trap(:TERM) { proxy.shutdown } +trap(:INT) { proxy.shutdown } + +proxy.start diff --git a/.gems/gems/faraday-0.9.0/script/release b/.gems/gems/faraday-0.9.0/script/release new file mode 100755 index 0000000..ed73118 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/release @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Usage: script/release +# Build the package, tag a commit, push it to origin, and then release the +# package publicly. + +set -e + +version="$(script/package | grep Version: | awk '{print $2}')" +[ -n "$version" ] || exit 1 + +git commit --allow-empty -a -m "Release $version" +git tag "v$version" +git push origin +git push origin "v$version" +git push legacy +git push legacy "v$version" +gem push pkg/*-${version}.gem diff --git a/.gems/gems/faraday-0.9.0/script/server b/.gems/gems/faraday-0.9.0/script/server new file mode 100755 index 0000000..f497d35 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/server @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +old_verbose, $VERBOSE = $VERBOSE, nil +begin + require File.expand_path('../../test/live_server', __FILE__) +ensure + $VERBOSE = old_verbose +end +require 'webrick' + +port = 4000 +if found = ARGV.index('-p') + port = ARGV[found + 1].to_i +end + +log_io = $stdout +log_io.sync = true + +webrick_opts = { + :Port => port, :Logger => WEBrick::Log::new(log_io), + :AccessLog => [[log_io, "[%{X-Faraday-Adapter}i] %m %U -> %s %b"]] +} + +if ENV['SSL_KEY'] + require 'openssl' + require 'webrick/https' + webrick_opts.update \ + :SSLEnable => true, + :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.read(ENV['SSL_KEY'])), + :SSLCertificate => OpenSSL::X509::Certificate.new(File.read(ENV['SSL_FILE'])), + :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE +end + +Rack::Handler::WEBrick.run(Faraday::LiveServer, webrick_opts) do |server| + trap(:INT) { server.stop } + trap(:TERM) { server.stop } +end diff --git a/.gems/gems/faraday-0.9.0/script/test b/.gems/gems/faraday-0.9.0/script/test new file mode 100755 index 0000000..beb9af2 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/script/test @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# Usage: script/test [file] [adapter]... -- [test/unit options] +# Runs the test suite against a local server spawned automatically in a +# thread. After tests are done, the server is shut down. +# +# If filename arguments are given, only those files are run. If arguments given +# are not filenames, they are taken as words that filter the list of files to run. +# +# Examples: +# +# $ script/test +# $ script/test test/env_test.rb +# $ script/test excon typhoeus +# +# # Run only tests matching /ssl/ for the net_http adapter, with SSL enabled. +# $ SSL=yes script/test net_http -- -n /ssl/ +# +# # Run against multiple rbenv versions +# $ RBENV_VERSIONS="1.9.3-p194 ree-1.8.7-2012.02" script/test +set -e + +if [[ "$RUBYOPT" != *"bundler/setup"* ]]; then + export RUBYOPT="-rbundler/setup $RUBYOPT" +fi + +port=3999 +proxy_port=3998 +scheme=http + +if [ "$SSL" = "yes" ]; then + scheme=https + if [ -z "$SSL_KEY" ] || [ -z "$SSL_FILE" ]; then + eval "$(script/generate_certs -s)" + fi +fi + +find_test_files() { + find "$1" -name '*_test.rb' +} + +filter_matching() { + pattern="$1" + shift + for line in "$@"; do + [[ $line == *"$pattern"* ]] && echo "$line" + done +} + +start_server() { + mkdir -p log + script/server -p $port >log/test.log 2>&1 & + echo $! +} + +start_proxy() { + mkdir -p log + script/proxy-server -p $proxy_port -u "faraday@test.local:there is cake" >log/proxy.log 2>&1 & + echo $! +} + +server_started() { + lsof -i :${1?} >/dev/null +} + +timestamp() { + date +%s +} + +wait_for_server() { + timeout=$(( `timestamp` + $1 )) + while true; do + if server_started "$2"; then + break + elif [ `timestamp` -gt "$timeout" ]; then + echo "timed out after $1 seconds" >&2 + return 1 + fi + done +} + +filtered= +IFS=$'\n' test_files=($(find_test_files "test")) +declare -a explicit_files + +# Process filter arguments: +# - test filenames as taken as-is +# - other words are taken as pattern to match the list of known files against +# - arguments after "--" are forwarded to the ruby process +while [ $# -gt 0 ]; do + arg="$1" + shift + if [ "$arg" = "--" ]; then + break + elif [ -f "$arg" ]; then + filtered=true + explicit_files[${#explicit_files[@]}+1]="$arg" + else + filtered=true + IFS=$'\n' explicit_files=( + ${explicit_files[@]} + $(filter_matching "$arg" "${test_files[@]}" || true) + ) + fi +done + +# If there were filter args, replace test files list with the results +if [ -n "$filtered" ]; then + if [ ${#explicit_files[@]} -eq 0 ]; then + echo "Error: no test files match" >&2 + exit 1 + else + test_files=(${explicit_files[@]}) + echo running "${test_files[@]}" + fi +fi + +# If there are any adapter tests, spin up the HTTP server +if [ -n "$(filter_matching "adapters" "${test_files[@]}")" ]; then + if server_started $port; then + echo "aborted: another instance of server running on $port" >&2 + exit 1 + fi + server_pid=$(start_server) + proxy_pid=$(start_proxy) + wait_for_server 30 $port || { + cat log/test.log + kill "$server_pid" + kill "$proxy_pid" + exit 1 + } + wait_for_server 5 $proxy_port + cleanup() { + if [ $? -ne 0 ] && [ -n "$TRAVIS" ]; then + cat log/test.log + fi + kill "$server_pid" + kill "$proxy_pid" + } + trap cleanup INT EXIT + export LIVE="${scheme}://localhost:${port}" + export LIVE_PROXY="http://faraday%40test.local:there%20is%20cake@localhost:${proxy_port}" +fi + +warnings="${TMPDIR:-/tmp}/faraday-warnings.$$" + +run_test_files() { + # Save warnings on stderr to a separate file + RUBYOPT="$RUBYOPT -w" ruby -e 'while f=ARGV.shift and f!="--"; load f; end' "${test_files[@]}" -- "$@" \ + 2> >(tee >(grep 'warning:' >"$warnings") | grep -v 'warning:') +} + +check_warnings() { + # Display Ruby warnings from this project's source files. Abort if any were found. + num="$(grep -F "$PWD" "$warnings" | grep -v "${PWD}/bundle" | sort | uniq -c | sort -rn | tee /dev/stderr | wc -l)" + rm -f "$warnings" + if [ "$num" -gt 0 ]; then + echo "FAILED: this test suite doesn't tolerate Ruby syntax warnings!" >&2 + exit 1 + fi +} + +if [ -n "$RBENV_VERSIONS" ]; then + IFS=' ' versions=($RBENV_VERSIONS) + for version in "${versions[@]}"; do + echo "[${version}]" + RBENV_VERSION="$version" run_test_files "$@" + done +else + run_test_files "$@" +fi + +check_warnings diff --git a/.gems/gems/faraday-0.9.0/test/adapters/default_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/default_test.rb new file mode 100644 index 0000000..400baab --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/default_test.rb @@ -0,0 +1,14 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class DefaultTest < Faraday::TestCase + + def adapter() :default end + + Integration.apply(self, :NonParallel) do + # default stack is not configured with Multipart + undef :test_POST_sends_files + end + + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/em_http_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/em_http_test.rb new file mode 100644 index 0000000..f122090 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/em_http_test.rb @@ -0,0 +1,20 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class EMHttpTest < Faraday::TestCase + + def adapter() :em_http end + + Integration.apply(self, :Parallel) do + # https://github.com/eventmachine/eventmachine/pull/289 + undef :test_timeout + + def test_binds_local_socket + host = '1.2.3.4' + conn = create_connection :request => { :bind => { :host => host } } + assert_equal host, conn.options[:bind][:host] + end + end unless jruby? and ssl_mode? + # https://github.com/eventmachine/eventmachine/issues/180 + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/em_synchrony_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/em_synchrony_test.rb new file mode 100644 index 0000000..59cdef7 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/em_synchrony_test.rb @@ -0,0 +1,20 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class EMSynchronyTest < Faraday::TestCase + + def adapter() :em_synchrony end + + Integration.apply(self, :Parallel) do + # https://github.com/eventmachine/eventmachine/pull/289 + undef :test_timeout + + def test_binds_local_socket + host = '1.2.3.4' + conn = create_connection :request => { :bind => { :host => host } } + #put conn.get('/who-am-i').body + assert_equal host, conn.options[:bind][:host] + end + end unless RUBY_VERSION < '1.9' or jruby? + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/excon_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/excon_test.rb new file mode 100644 index 0000000..f7b9967 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/excon_test.rb @@ -0,0 +1,20 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class ExconTest < Faraday::TestCase + + def adapter() :excon end + + Integration.apply(self, :NonParallel) do + # https://github.com/geemus/excon/issues/126 ? + undef :test_timeout if ssl_mode? + + # Excon lets OpenSSL::SSL::SSLError be raised without any way to + # distinguish whether it happened because of a 407 proxy response + undef :test_proxy_auth_fail if ssl_mode? + + # https://github.com/geemus/excon/issues/358 + undef :test_connection_error if RUBY_VERSION >= '2.1.0' + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/httpclient_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/httpclient_test.rb new file mode 100644 index 0000000..ba93ccc --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/httpclient_test.rb @@ -0,0 +1,21 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class HttpclientTest < Faraday::TestCase + + def adapter() :httpclient end + + Integration.apply(self, :NonParallel) do + def setup + require 'httpclient' unless defined?(HTTPClient) + HTTPClient::NO_PROXY_HOSTS.delete('localhost') + end + + def test_binds_local_socket + host = '1.2.3.4' + conn = create_connection :request => { :bind => { :host => host } } + assert_equal host, conn.options[:bind][:host] + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/integration.rb b/.gems/gems/faraday-0.9.0/test/adapters/integration.rb new file mode 100644 index 0000000..45445fe --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/integration.rb @@ -0,0 +1,254 @@ +require 'forwardable' +require File.expand_path("../../helper", __FILE__) +Faraday.require_lib 'autoload' + +module Adapters + # Adapter integration tests. To use, implement two methods: + # + # `#adapter` required. returns a symbol for the adapter middleware name + # `#adapter_options` optional. extra arguments for building an adapter + module Integration + def self.apply(base, *extra_features) + if base.live_server? + features = [:Common] + features.concat extra_features + features << :SSL if base.ssl_mode? + features.each {|name| base.send(:include, self.const_get(name)) } + yield if block_given? + elsif !defined? @warned + warn "Warning: Not running integration tests against a live server." + warn "Start the server `ruby test/live_server.rb` and set the LIVE=1 env variable." + @warned = true + end + end + + module Parallel + def test_in_parallel + resp1, resp2 = nil, nil + + connection = create_connection + connection.in_parallel do + resp1 = connection.get('echo?a=1') + resp2 = connection.get('echo?b=2') + assert connection.in_parallel? + assert_nil resp1.body + assert_nil resp2.body + end + assert !connection.in_parallel? + assert_equal 'get ?{"a"=>"1"}', resp1.body + assert_equal 'get ?{"b"=>"2"}', resp2.body + end + end + + module NonParallel + def test_no_parallel_support + connection = create_connection + response = nil + + err = capture_warnings do + connection.in_parallel do + response = connection.get('echo').body + end + end + assert response + assert_match "no parallel-capable adapter on Faraday stack", err + assert_match __FILE__, err + end + end + + module Compression + def test_GET_handles_compression + res = get('echo_header', :name => 'accept-encoding') + assert_match(/gzip;.+\bdeflate\b/, res.body) + end + end + + module SSL + def test_GET_ssl_fails_with_bad_cert + ca_file = 'tmp/faraday-different-ca-cert.crt' + conn = create_connection(:ssl => {:ca_file => ca_file}) + err = assert_raises Faraday::SSLError do + conn.get('/ssl') + end + assert_includes err.message, "certificate" + end + end + + module Common + extend Forwardable + def_delegators :create_connection, :get, :head, :put, :post, :patch, :delete, :run_request + + def test_GET_retrieves_the_response_body + assert_equal 'get', get('echo').body + end + + def test_GET_send_url_encoded_params + assert_equal %(get ?{"name"=>"zack"}), get('echo', :name => 'zack').body + end + + def test_GET_retrieves_the_response_headers + response = get('echo') + assert_match(/text\/plain/, response.headers['Content-Type'], 'original case fail') + assert_match(/text\/plain/, response.headers['content-type'], 'lowercase fail') + end + + def test_GET_handles_headers_with_multiple_values + assert_equal 'one, two', get('multi').headers['set-cookie'] + end + + def test_GET_with_body + response = get('echo') do |req| + req.body = {'bodyrock' => true} + end + assert_equal %(get {"bodyrock"=>"true"}), response.body + end + + def test_GET_sends_user_agent + response = get('echo_header', {:name => 'user-agent'}, :user_agent => 'Agent Faraday') + assert_equal 'Agent Faraday', response.body + end + + def test_GET_ssl + expected = self.class.ssl_mode?.to_s + assert_equal expected, get('ssl').body + end + + def test_POST_send_url_encoded_params + assert_equal %(post {"name"=>"zack"}), post('echo', :name => 'zack').body + end + + def test_POST_send_url_encoded_nested_params + resp = post('echo', 'name' => {'first' => 'zack'}) + assert_equal %(post {"name"=>{"first"=>"zack"}}), resp.body + end + + def test_POST_retrieves_the_response_headers + assert_match(/text\/plain/, post('echo').headers['content-type']) + end + + def test_POST_sends_files + resp = post('file') do |req| + req.body = {'uploaded_file' => Faraday::UploadIO.new(__FILE__, 'text/x-ruby')} + end + assert_equal "file integration.rb text/x-ruby #{File.size(__FILE__)}", resp.body + end + + def test_PUT_send_url_encoded_params + assert_equal %(put {"name"=>"zack"}), put('echo', :name => 'zack').body + end + + def test_PUT_send_url_encoded_nested_params + resp = put('echo', 'name' => {'first' => 'zack'}) + assert_equal %(put {"name"=>{"first"=>"zack"}}), resp.body + end + + def test_PUT_retrieves_the_response_headers + assert_match(/text\/plain/, put('echo').headers['content-type']) + end + + def test_PATCH_send_url_encoded_params + assert_equal %(patch {"name"=>"zack"}), patch('echo', :name => 'zack').body + end + + def test_OPTIONS + resp = run_request(:options, 'echo', nil, {}) + assert_equal 'options', resp.body + end + + def test_HEAD_retrieves_no_response_body + assert_equal '', head('echo').body + end + + def test_HEAD_retrieves_the_response_headers + assert_match(/text\/plain/, head('echo').headers['content-type']) + end + + def test_DELETE_retrieves_the_response_headers + assert_match(/text\/plain/, delete('echo').headers['content-type']) + end + + def test_DELETE_retrieves_the_body + assert_equal %(delete), delete('echo').body + end + + def test_timeout + conn = create_connection(:request => {:timeout => 1, :open_timeout => 1}) + assert_raises Faraday::Error::TimeoutError do + conn.get '/slow' + end + end + + def test_connection_error + assert_raises Faraday::Error::ConnectionFailed do + get 'http://localhost:4' + end + end + + def test_proxy + proxy_uri = URI(ENV['LIVE_PROXY']) + conn = create_connection(:proxy => proxy_uri) + + res = conn.get '/echo' + assert_equal 'get', res.body + + unless self.class.ssl_mode? + # proxy can't append "Via" header for HTTPS responses + assert_match(/:#{proxy_uri.port}$/, res['via']) + end + end + + def test_proxy_auth_fail + proxy_uri = URI(ENV['LIVE_PROXY']) + proxy_uri.password = 'WRONG' + conn = create_connection(:proxy => proxy_uri) + + err = assert_raises Faraday::Error::ConnectionFailed do + conn.get '/echo' + end + + unless self.class.ssl_mode? && self.class.jruby? + # JRuby raises "End of file reached" which cannot be distinguished from a 407 + assert_equal %{407 "Proxy Authentication Required "}, err.message + end + end + + def test_empty_body_response_represented_as_blank_string + response = get('204') + assert_equal '', response.body + end + + def adapter + raise NotImplementedError.new("Need to override #adapter") + end + + # extra options to pass when building the adapter + def adapter_options + [] + end + + def create_connection(options = {}) + if adapter == :default + builder_block = nil + else + builder_block = Proc.new do |b| + b.request :multipart + b.request :url_encoded + b.adapter adapter, *adapter_options + end + end + + server = self.class.live_server + url = '%s://%s:%d' % [server.scheme, server.host, server.port] + + options[:ssl] ||= {} + options[:ssl][:ca_file] ||= ENV['SSL_FILE'] + + Faraday::Connection.new(url, options, &builder_block).tap do |conn| + conn.headers['X-Faraday-Adapter'] = adapter.to_s + adapter_handler = conn.builder.handlers.last + conn.builder.insert_before adapter_handler, Faraday::Response::RaiseError + end + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/logger_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/logger_test.rb new file mode 100644 index 0000000..7a71ca6 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/logger_test.rb @@ -0,0 +1,37 @@ +require File.expand_path('../../helper', __FILE__) +require 'stringio' +require 'logger' + +module Adapters + class LoggerTest < Faraday::TestCase + def setup + @io = StringIO.new + @logger = Logger.new(@io) + @logger.level = Logger::DEBUG + + @conn = Faraday.new do |b| + b.response :logger, @logger + b.adapter :test do |stubs| + stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] } + end + end + @resp = @conn.get '/hello', nil, :accept => 'text/html' + end + + def test_still_returns_output + assert_equal 'hello', @resp.body + end + + def test_logs_method_and_url + assert_match "get http:/hello", @io.string + end + + def test_logs_request_headers + assert_match %(Accept: "text/html), @io.string + end + + def test_logs_response_headers + assert_match %(Content-Type: "text/html), @io.string + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/net_http_persistent_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/net_http_persistent_test.rb new file mode 100644 index 0000000..edd986a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/net_http_persistent_test.rb @@ -0,0 +1,20 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class NetHttpPersistentTest < Faraday::TestCase + + def adapter() :net_http_persistent end + + Integration.apply(self, :NonParallel) do + def setup + if defined?(Net::HTTP::Persistent) + # work around problems with mixed SSL certificates + # https://github.com/drbrain/net-http-persistent/issues/45 + http = Net::HTTP::Persistent.new('Faraday') + http.ssl_cleanup(4) + end + end if ssl_mode? + end + + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/net_http_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/net_http_test.rb new file mode 100644 index 0000000..810fad4 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/net_http_test.rb @@ -0,0 +1,14 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class NetHttpTest < Faraday::TestCase + + def adapter() :net_http end + + behaviors = [:NonParallel] + behaviors << :Compression if RUBY_VERSION >= '1.9' + + Integration.apply(self, *behaviors) + + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/patron_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/patron_test.rb new file mode 100644 index 0000000..828d7fb --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/patron_test.rb @@ -0,0 +1,20 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class Patron < Faraday::TestCase + + def adapter() :patron end + + Integration.apply(self, :NonParallel) do + # https://github.com/toland/patron/issues/34 + undef :test_PATCH_send_url_encoded_params + + # https://github.com/toland/patron/issues/52 + undef :test_GET_with_body + + # no support for SSL peer verification + undef :test_GET_ssl_fails_with_bad_cert if ssl_mode? + end unless jruby? + + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/rack_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/rack_test.rb new file mode 100644 index 0000000..545099c --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/rack_test.rb @@ -0,0 +1,31 @@ +require File.expand_path("../integration", __FILE__) +require File.expand_path('../../live_server', __FILE__) + +module Adapters + class RackTest < Faraday::TestCase + + def adapter() :rack end + + def adapter_options + [Faraday::LiveServer] + end + + # no Integration.apply because this doesn't require a server as a separate process + include Integration::Common + include Integration::NonParallel + + # not using shared test because error is swallowed by Sinatra + def test_timeout + conn = create_connection(:request => {:timeout => 1, :open_timeout => 1}) + begin + conn.get '/slow' + rescue Faraday::Error::ClientError + end + end + + # test not applicable + undef test_connection_error + undef test_proxy + undef test_proxy_auth_fail + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/test_middleware_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/test_middleware_test.rb new file mode 100644 index 0000000..2acd4a4 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/test_middleware_test.rb @@ -0,0 +1,114 @@ +require File.expand_path('../../helper', __FILE__) + +module Adapters + class TestMiddleware < Faraday::TestCase + Stubs = Faraday::Adapter.lookup_middleware(:test)::Stubs + def setup + @stubs = Stubs.new + @conn = Faraday.new do |builder| + builder.adapter :test, @stubs + end + @stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] } + @resp = @conn.get('/hello') + end + + def test_middleware_with_simple_path_sets_status + assert_equal 200, @resp.status + end + + def test_middleware_with_simple_path_sets_headers + assert_equal 'text/html', @resp.headers['Content-Type'] + end + + def test_middleware_with_simple_path_sets_body + assert_equal 'hello', @resp.body + end + + def test_middleware_can_be_called_several_times + assert_equal 'hello', @conn.get("/hello").body + end + + def test_middleware_with_get_params + @stubs.get('/param?a=1') { [200, {}, 'a'] } + assert_equal 'a', @conn.get('/param?a=1').body + end + + def test_middleware_ignores_unspecified_get_params + @stubs.get('/optional?a=1') { [200, {}, 'a'] } + assert_equal 'a', @conn.get('/optional?a=1&b=1').body + assert_equal 'a', @conn.get('/optional?a=1').body + assert_raises Faraday::Adapter::Test::Stubs::NotFound do + @conn.get('/optional') + end + end + + def test_middleware_with_http_headers + @stubs.get('/yo', { 'X-HELLO' => 'hello' }) { [200, {}, 'a'] } + @stubs.get('/yo') { [200, {}, 'b'] } + assert_equal 'a', @conn.get('/yo') { |env| env.headers['X-HELLO'] = 'hello' }.body + assert_equal 'b', @conn.get('/yo').body + end + + def test_middleware_allow_different_outcomes_for_the_same_request + @stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] } + @stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'world'] } + assert_equal 'hello', @conn.get("/hello").body + assert_equal 'world', @conn.get("/hello").body + end + + def test_yields_env_to_stubs + @stubs.get '/hello' do |env| + assert_equal '/hello', env[:url].path + assert_equal 'foo.com', env[:url].host + assert_equal '1', env[:params]['a'] + assert_equal 'text/plain', env[:request_headers]['Accept'] + [200, {}, 'a'] + end + + @conn.headers['Accept'] = 'text/plain' + assert_equal 'a', @conn.get('http://foo.com/hello?a=1').body + end + + def test_parses_params_with_default_encoder + @stubs.get '/hello' do |env| + assert_equal '1', env[:params]['a']['b'] + [200, {}, 'a'] + end + + assert_equal 'a', @conn.get('http://foo.com/hello?a[b]=1').body + end + + def test_parses_params_with_nested_encoder + @stubs.get '/hello' do |env| + assert_equal '1', env[:params]['a']['b'] + [200, {}, 'a'] + end + + @conn.options.params_encoder = Faraday::NestedParamsEncoder + assert_equal 'a', @conn.get('http://foo.com/hello?a[b]=1').body + end + + def test_parses_params_with_flat_encoder + @stubs.get '/hello' do |env| + assert_equal '1', env[:params]['a[b]'] + [200, {}, 'a'] + end + + @conn.options.params_encoder = Faraday::FlatParamsEncoder + assert_equal 'a', @conn.get('http://foo.com/hello?a[b]=1').body + end + + def test_raises_an_error_if_no_stub_is_found_for_request + assert_raises Stubs::NotFound do + @conn.get('/invalid'){ [200, {}, []] } + end + end + + def test_raises_an_error_if_no_stub_is_found_for_request_without_this_header + @stubs.get('/yo', { 'X-HELLO' => 'hello' }) { [200, {}, 'a'] } + assert_raises Faraday::Adapter::Test::Stubs::NotFound do + @conn.get('/yo') + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/adapters/typhoeus_test.rb b/.gems/gems/faraday-0.9.0/test/adapters/typhoeus_test.rb new file mode 100644 index 0000000..5ddc50b --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/adapters/typhoeus_test.rb @@ -0,0 +1,28 @@ +require File.expand_path('../integration', __FILE__) + +module Adapters + class TyphoeusTest < Faraday::TestCase + + def adapter() :typhoeus end + + Integration.apply(self, :Parallel) do + # https://github.com/dbalatero/typhoeus/issues/75 + undef :test_GET_with_body + + # Not a Typhoeus bug, but WEBrick inability to handle "100-continue" + # which libcurl seems to generate for this particular request: + undef :test_POST_sends_files + + # inconsistent outcomes ranging from successful response to connection error + undef :test_proxy_auth_fail if ssl_mode? + + def test_binds_local_socket + host = '1.2.3.4' + conn = create_connection :request => { :bind => { :host => host } } + assert_equal host, conn.options[:bind][:host] + end + + end unless jruby? + end +end + diff --git a/.gems/gems/faraday-0.9.0/test/authentication_middleware_test.rb b/.gems/gems/faraday-0.9.0/test/authentication_middleware_test.rb new file mode 100644 index 0000000..1fbad61 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/authentication_middleware_test.rb @@ -0,0 +1,65 @@ +require File.expand_path('../helper', __FILE__) + +class AuthenticationMiddlewareTest < Faraday::TestCase + def conn + Faraday::Connection.new('http://example.net/') do |builder| + yield(builder) + builder.adapter :test do |stub| + stub.get('/auth-echo') do |env| + [200, {}, env[:request_headers]['Authorization']] + end + end + end + end + + def test_basic_middleware_adds_basic_header + response = conn { |b| b.request :basic_auth, 'aladdin', 'opensesame' }.get('/auth-echo') + assert_equal 'Basic YWxhZGRpbjpvcGVuc2VzYW1l', response.body + end + + def test_basic_middleware_adds_basic_header_correctly_with_long_values + response = conn { |b| b.request :basic_auth, 'A' * 255, '' }.get('/auth-echo') + assert_equal "Basic #{'QUFB' * 85}Og==", response.body + end + + def test_basic_middleware_does_not_interfere_with_existing_authorization + response = conn { |b| b.request :basic_auth, 'aladdin', 'opensesame' }. + get('/auth-echo', nil, :authorization => 'Token token="bar"') + assert_equal 'Token token="bar"', response.body + end + + def test_token_middleware_adds_token_header + response = conn { |b| b.request :token_auth, 'quux' }.get('/auth-echo') + assert_equal 'Token token="quux"', response.body + end + + def test_token_middleware_includes_other_values_if_provided + response = conn { |b| + b.request :token_auth, 'baz', :foo => 42 + }.get('/auth-echo') + assert_match(/^Token /, response.body) + assert_match(/token="baz"/, response.body) + assert_match(/foo="42"/, response.body) + end + + def test_token_middleware_does_not_interfere_with_existing_authorization + response = conn { |b| b.request :token_auth, 'quux' }. + get('/auth-echo', nil, :authorization => 'Token token="bar"') + assert_equal 'Token token="bar"', response.body + end + + def test_authorization_middleware_with_string + response = conn { |b| + b.request :authorization, 'custom', 'abc def' + }.get('/auth-echo') + assert_match(/^custom abc def$/, response.body) + end + + def test_authorization_middleware_with_hash + response = conn { |b| + b.request :authorization, 'baz', :foo => 42 + }.get('/auth-echo') + assert_match(/^baz /, response.body) + assert_match(/foo="42"/, response.body) + end +end diff --git a/.gems/gems/faraday-0.9.0/test/composite_read_io_test.rb b/.gems/gems/faraday-0.9.0/test/composite_read_io_test.rb new file mode 100644 index 0000000..0632dee --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/composite_read_io_test.rb @@ -0,0 +1,111 @@ +require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) +require 'stringio' + +class CompositeReadIOTest < Faraday::TestCase + Part = Struct.new(:to_io) do + def length() to_io.string.length end + end + + def part(str) + Part.new StringIO.new(str) + end + + def composite_io(*parts) + Faraday::CompositeReadIO.new(*parts) + end + + def test_empty + io = composite_io + assert_equal 0, io.length + assert_equal "", io.read + end + + def test_empty_returns_nil_for_limited_read + assert_nil composite_io.read(1) + end + + def test_empty_parts_returns_nil_for_limited_read + io = composite_io(part(""), part("")) + assert_nil io.read(1) + end + + def test_multipart_read_all + io = composite_io(part("abcd"), part("1234")) + assert_equal 8, io.length + assert_equal "abcd1234", io.read + end + + def test_multipart_read_limited + io = composite_io(part("abcd"), part("1234")) + assert_equal "abc", io.read(3) + assert_equal "d12", io.read(3) + assert_equal "34", io.read(3) + assert_equal nil, io.read(3) + assert_equal nil, io.read(3) + end + + def test_multipart_read_limited_size_larger_than_part + io = composite_io(part("abcd"), part("1234")) + assert_equal "abcd12", io.read(6) + assert_equal "34", io.read(6) + assert_equal nil, io.read(6) + end + + def test_multipart_read_with_blank_parts + io = composite_io(part(""), part("abcd"), part(""), part("1234"), part("")) + assert_equal "abcd12", io.read(6) + assert_equal "34", io.read(6) + assert_equal nil, io.read(6) + end + + def test_multipart_rewind + io = composite_io(part("abcd"), part("1234")) + assert_equal "abc", io.read(3) + assert_equal "d12", io.read(3) + io.rewind + assert_equal "abc", io.read(3) + assert_equal "d1234", io.read(5) + assert_equal nil, io.read(3) + io.rewind + assert_equal "ab", io.read(2) + end + + # JRuby enforces types to copy_stream to be String or IO + if IO.respond_to?(:copy_stream) && !jruby? + def test_compatible_with_copy_stream + target_io = StringIO.new + def target_io.ensure_open_and_writable + # Rubinius compatibility + end + io = composite_io(part("abcd"), part("1234")) + + Faraday::Timer.timeout(1) do + IO.copy_stream(io, target_io) + end + assert_equal "abcd1234", target_io.string + end + end + + unless RUBY_VERSION < '1.9' + def test_read_from_multibyte + File.open(File.dirname(__FILE__) + '/multibyte.txt') do |utf8| + io = composite_io(part("\x86"), Part.new(utf8)) + assert_equal bin("\x86\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB\n"), io.read + end + end + + def test_limited_from_multibyte + File.open(File.dirname(__FILE__) + '/multibyte.txt') do |utf8| + io = composite_io(part("\x86"), Part.new(utf8)) + assert_equal bin("\x86\xE3\x83"), io.read(3) + assert_equal bin("\x95\xE3\x82"), io.read(3) + assert_equal bin("\xA1\xE3\x82\xA4\xE3\x83\xAB\n"), io.read(8) + end + end + end + + def bin(str) + str.force_encoding("BINARY") if str.respond_to?(:force_encoding) + str + end +end diff --git a/.gems/gems/faraday-0.9.0/test/connection_test.rb b/.gems/gems/faraday-0.9.0/test/connection_test.rb new file mode 100644 index 0000000..e164736 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/connection_test.rb @@ -0,0 +1,522 @@ +require File.expand_path('../helper', __FILE__) + +class TestConnection < Faraday::TestCase + + def with_env(key, proxy) + old_value = ENV.fetch(key, false) + ENV[key] = proxy + begin + yield + ensure + if old_value == false + ENV.delete key + else + ENV[key] = old_value + end + end + end + + def test_initialize_parses_host_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com" + assert_equal 'sushi.com', conn.host + end + + def test_initialize_inherits_default_port_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com" + assert_equal 80, conn.port + end + + def test_initialize_parses_scheme_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com" + assert_equal 'http', conn.scheme + end + + def test_initialize_parses_port_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com:815" + assert_equal 815, conn.port + end + + def test_initialize_parses_nil_path_prefix_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com" + assert_equal '/', conn.path_prefix + end + + def test_initialize_parses_path_prefix_out_of_given_url + conn = Faraday::Connection.new "http://sushi.com/fish" + assert_equal '/fish', conn.path_prefix + end + + def test_initialize_parses_path_prefix_out_of_given_url_option + conn = Faraday::Connection.new :url => "http://sushi.com/fish" + assert_equal '/fish', conn.path_prefix + end + + def test_initialize_stores_default_params_from_options + conn = Faraday::Connection.new :params => {:a => 1} + assert_equal({'a' => 1}, conn.params) + end + + def test_initialize_stores_default_params_from_uri + conn = Faraday::Connection.new "http://sushi.com/fish?a=1" + assert_equal({'a' => '1'}, conn.params) + end + + def test_initialize_stores_default_params_from_uri_and_options + conn = Faraday::Connection.new "http://sushi.com/fish?a=1&b=2", :params => {'a' => 3} + assert_equal({'a' => 3, 'b' => '2'}, conn.params) + end + + def test_initialize_stores_default_headers_from_options + conn = Faraday::Connection.new :headers => {:user_agent => 'Faraday'} + assert_equal 'Faraday', conn.headers['User-agent'] + end + + def test_basic_auth_sets_header + conn = Faraday::Connection.new + assert_nil conn.headers['Authorization'] + + conn.basic_auth 'Aladdin', 'open sesame' + assert auth = conn.headers['Authorization'] + assert_equal 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', auth + end + + def test_auto_parses_basic_auth_from_url_and_unescapes + conn = Faraday::Connection.new :url => "http://foo%40bar.com:pass%20word@sushi.com/fish" + assert auth = conn.headers['Authorization'] + assert_equal Faraday::Request::BasicAuthentication.header("foo@bar.com", "pass word"), auth + end + + def test_token_auth_sets_header + conn = Faraday::Connection.new + assert_nil conn.headers['Authorization'] + + conn.token_auth 'abcdef', :nonce => 'abc' + assert auth = conn.headers['Authorization'] + assert_match(/^Token /, auth) + assert_match(/token="abcdef"/, auth) + assert_match(/nonce="abc"/, auth) + end + + def test_build_exclusive_url_uses_connection_host_as_default_uri_host + conn = Faraday::Connection.new + conn.host = 'sushi.com' + uri = conn.build_exclusive_url("/sake.html") + assert_equal 'sushi.com', uri.host + end + + def test_build_exclusive_url_overrides_connection_port_for_absolute_urls + conn = Faraday::Connection.new + conn.port = 23 + uri = conn.build_exclusive_url("http://sushi.com") + assert_equal 80, uri.port + end + + def test_build_exclusive_url_uses_connection_scheme_as_default_uri_scheme + conn = Faraday::Connection.new 'http://sushi.com' + uri = conn.build_exclusive_url("/sake.html") + assert_equal 'http', uri.scheme + end + + def test_build_exclusive_url_uses_connection_path_prefix_to_customize_path + conn = Faraday::Connection.new + conn.path_prefix = '/fish' + uri = conn.build_exclusive_url("sake.html") + assert_equal '/fish/sake.html', uri.path + end + + def test_build_exclusive_url_uses_root_connection_path_prefix_to_customize_path + conn = Faraday::Connection.new + conn.path_prefix = '/' + uri = conn.build_exclusive_url("sake.html") + assert_equal '/sake.html', uri.path + end + + def test_build_exclusive_url_forces_connection_path_prefix_to_be_absolute + conn = Faraday::Connection.new + conn.path_prefix = 'fish' + uri = conn.build_exclusive_url("sake.html") + assert_equal '/fish/sake.html', uri.path + end + + def test_build_exclusive_url_ignores_connection_path_prefix_trailing_slash + conn = Faraday::Connection.new + conn.path_prefix = '/fish/' + uri = conn.build_exclusive_url("sake.html") + assert_equal '/fish/sake.html', uri.path + end + + def test_build_exclusive_url_allows_absolute_uri_to_ignore_connection_path_prefix + conn = Faraday::Connection.new + conn.path_prefix = '/fish' + uri = conn.build_exclusive_url("/sake.html") + assert_equal '/sake.html', uri.path + end + + def test_build_exclusive_url_parses_url_params_into_path + conn = Faraday::Connection.new + uri = conn.build_exclusive_url("http://sushi.com/sake.html") + assert_equal '/sake.html', uri.path + end + + def test_build_exclusive_url_doesnt_add_ending_slash_given_nil_url + conn = Faraday::Connection.new + conn.url_prefix = "http://sushi.com/nigiri" + uri = conn.build_exclusive_url + assert_equal "/nigiri", uri.path + end + + def test_build_exclusive_url_doesnt_add_ending_slash_given_empty_url + conn = Faraday::Connection.new + conn.url_prefix = "http://sushi.com/nigiri" + uri = conn.build_exclusive_url('') + assert_equal "/nigiri", uri.path + end + + def test_build_exclusive_url_doesnt_use_connection_params + conn = Faraday::Connection.new "http://sushi.com/nigiri" + conn.params = {:a => 1} + assert_equal "http://sushi.com/nigiri", conn.build_exclusive_url.to_s + end + + def test_build_exclusive_url_uses_argument_params + conn = Faraday::Connection.new "http://sushi.com/nigiri" + conn.params = {:a => 1} + params = Faraday::Utils::ParamsHash.new + params[:a] = 2 + url = conn.build_exclusive_url(nil, params) + assert_equal "http://sushi.com/nigiri?a=2", url.to_s + end + + def test_build_url_uses_params + conn = Faraday::Connection.new "http://sushi.com/nigiri" + conn.params = {:a => 1, :b => 1} + assert_equal "http://sushi.com/nigiri?a=1&b=1", conn.build_url.to_s + end + + def test_build_url_merges_params + conn = Faraday::Connection.new "http://sushi.com/nigiri" + conn.params = {:a => 1, :b => 1} + url = conn.build_url(nil, :b => 2, :c => 3) + assert_equal "http://sushi.com/nigiri?a=1&b=2&c=3", url.to_s + end + + def test_env_url_parses_url_params_into_query + uri = env_url("http://sushi.com/sake.html", 'a[b]' => '1 + 2') + assert_equal "a%5Bb%5D=1+%2B+2", uri.query + end + + def test_env_url_escapes_per_spec + uri = env_url('http:/', 'a' => '1+2 foo~bar.-baz') + assert_equal "a=1%2B2+foo~bar.-baz", uri.query + end + + def test_env_url_bracketizes_nested_params_in_query + url = env_url nil, 'a' => {'b' => 'c'} + assert_equal "a%5Bb%5D=c", url.query + end + + def test_env_url_bracketizes_repeated_params_in_query + uri = env_url("http://sushi.com/sake.html", 'a' => [1, 2]) + assert_equal "a%5B%5D=1&a%5B%5D=2", uri.query + end + + def test_env_url_without_braketizing_repeated_params_in_query + uri = env_url 'http://sushi.com', 'a' => [1, 2] do |conn| + conn.options.params_encoder = Faraday::FlatParamsEncoder + end + assert_equal "a=1&a=2", uri.query + end + + def test_build_exclusive_url_parses_url + conn = Faraday::Connection.new + uri = conn.build_exclusive_url("http://sushi.com/sake.html") + assert_equal "http", uri.scheme + assert_equal "sushi.com", uri.host + assert_equal '/sake.html', uri.path + end + + def test_build_exclusive_url_parses_url_and_changes_scheme + conn = Faraday::Connection.new :url => "http://sushi.com/sushi" + conn.scheme = 'https' + uri = conn.build_exclusive_url("sake.html") + assert_equal 'https://sushi.com/sushi/sake.html', uri.to_s + end + + def test_build_exclusive_url_joins_url_to_base_with_ending_slash + conn = Faraday::Connection.new :url => "http://sushi.com/sushi/" + uri = conn.build_exclusive_url("sake.html") + assert_equal 'http://sushi.com/sushi/sake.html', uri.to_s + end + + def test_build_exclusive_url_used_default_base_with_ending_slash + conn = Faraday::Connection.new :url => "http://sushi.com/sushi/" + uri = conn.build_exclusive_url + assert_equal 'http://sushi.com/sushi/', uri.to_s + end + + def test_build_exclusive_url_overrides_base + conn = Faraday::Connection.new :url => "http://sushi.com/sushi/" + uri = conn.build_exclusive_url('/sake/') + assert_equal 'http://sushi.com/sake/', uri.to_s + end + + def test_build_exclusive_url_handles_uri_instances + conn = Faraday::Connection.new + uri = conn.build_exclusive_url(URI('/sake.html')) + assert_equal '/sake.html', uri.path + end + + def test_proxy_accepts_string + with_env 'http_proxy', "http://duncan.proxy.com:80" do + conn = Faraday::Connection.new + conn.proxy 'http://proxy.com' + assert_equal 'proxy.com', conn.proxy.host + end + end + + def test_proxy_accepts_uri + with_env 'http_proxy', "http://duncan.proxy.com:80" do + conn = Faraday::Connection.new + conn.proxy URI.parse('http://proxy.com') + assert_equal 'proxy.com', conn.proxy.host + end + end + + def test_proxy_accepts_hash_with_string_uri + with_env 'http_proxy', "http://duncan.proxy.com:80" do + conn = Faraday::Connection.new + conn.proxy :uri => 'http://proxy.com', :user => 'rick' + assert_equal 'proxy.com', conn.proxy.host + assert_equal 'rick', conn.proxy.user + end + end + + def test_proxy_accepts_hash + with_env 'http_proxy', "http://duncan.proxy.com:80" do + conn = Faraday::Connection.new + conn.proxy :uri => URI.parse('http://proxy.com'), :user => 'rick' + assert_equal 'proxy.com', conn.proxy.host + assert_equal 'rick', conn.proxy.user + end + end + + def test_proxy_accepts_http_env + with_env 'http_proxy', "http://duncan.proxy.com:80" do + conn = Faraday::Connection.new + assert_equal 'duncan.proxy.com', conn.proxy.host + end + end + + def test_proxy_accepts_http_env_with_auth + with_env 'http_proxy', "http://a%40b:my%20pass@duncan.proxy.com:80" do + conn = Faraday::Connection.new + assert_equal 'a@b', conn.proxy.user + assert_equal 'my pass', conn.proxy.password + end + end + + def test_proxy_accepts_env_without_scheme + with_env 'http_proxy', "localhost:8888" do + uri = Faraday::Connection.new.proxy[:uri] + assert_equal 'localhost', uri.host + assert_equal 8888, uri.port + end + end + + def test_no_proxy_from_env + with_env 'http_proxy', nil do + conn = Faraday::Connection.new + assert_equal nil, conn.proxy + end + end + + def test_no_proxy_from_blank_env + with_env 'http_proxy', '' do + conn = Faraday::Connection.new + assert_equal nil, conn.proxy + end + end + + def test_proxy_doesnt_accept_uppercase_env + with_env 'HTTP_PROXY', "http://localhost:8888/" do + conn = Faraday::Connection.new + assert_nil conn.proxy + end + end + + def test_proxy_requires_uri + conn = Faraday::Connection.new + assert_raises ArgumentError do + conn.proxy :uri => :bad_uri, :user => 'rick' + end + end + + def test_dups_connection_object + conn = Faraday::Connection.new 'http://sushi.com/foo', + :ssl => { :verify => :none }, + :headers => {'content-type' => 'text/plain'}, + :params => {'a'=>'1'} + + other = conn.dup + + assert_equal conn.build_exclusive_url, other.build_exclusive_url + assert_equal 'text/plain', other.headers['content-type'] + assert_equal '1', other.params['a'] + + other.basic_auth('', '') + other.headers['content-length'] = 12 + other.params['b'] = '2' + + assert_equal 2, other.builder.handlers.size + assert_equal 2, conn.builder.handlers.size + assert !conn.headers.key?('content-length') + assert !conn.params.key?('b') + end + + def test_initialize_with_false_option + conn = Faraday::Connection.new :ssl => {:verify => false} + assert !conn.ssl.verify? + end + + def test_init_with_block + conn = Faraday::Connection.new { } + assert_equal 0, conn.builder.handlers.size + end + + def test_init_with_block_yields_connection + conn = Faraday::Connection.new(:params => {'a'=>'1'}) { |faraday| + faraday.adapter :net_http + faraday.url_prefix = 'http://sushi.com/omnom' + assert_equal '1', faraday.params['a'] + } + assert_equal 1, conn.builder.handlers.size + assert_equal '/omnom', conn.path_prefix + end + + def env_url(url, params) + conn = Faraday::Connection.new(url, :params => params) + yield(conn) if block_given? + req = conn.build_request(:get) + req.to_env(conn).url + end +end + +class TestRequestParams < Faraday::TestCase + def create_connection(*args) + @conn = Faraday::Connection.new(*args) do |conn| + yield(conn) if block_given? + class << conn.builder + undef app + def app() lambda { |env| env } end + end + end + end + + def assert_query_equal(expected, query) + assert_equal expected, query.split('&').sort + end + + def with_default_params_encoder(encoder) + old_encoder = Faraday::Utils.default_params_encoder + begin + Faraday::Utils.default_params_encoder = encoder + yield + ensure + Faraday::Utils.default_params_encoder = old_encoder + end + end + + def test_merges_connection_and_request_params + create_connection 'http://a.co/?token=abc', :params => {'format' => 'json'} + query = get '?page=1', :limit => 5 + assert_query_equal %w[format=json limit=5 page=1 token=abc], query + end + + def test_overrides_connection_params + create_connection 'http://a.co/?a=a&b=b&c=c', :params => {:a => 'A'} do |conn| + conn.params[:b] = 'B' + assert_equal 'c', conn.params[:c] + end + assert_query_equal %w[a=A b=B c=c], get + end + + def test_all_overrides_connection_params + create_connection 'http://a.co/?a=a', :params => {:c => 'c'} do |conn| + conn.params = {'b' => 'b'} + end + assert_query_equal %w[b=b], get + end + + def test_overrides_request_params + create_connection + query = get '?p=1&a=a', :p => 2 + assert_query_equal %w[a=a p=2], query + end + + def test_overrides_request_params_block + create_connection + query = get '?p=1&a=a', :p => 2 do |req| + req.params[:p] = 3 + end + assert_query_equal %w[a=a p=3], query + end + + def test_overrides_request_params_block_url + create_connection + query = get nil, :p => 2 do |req| + req.url '?p=1&a=a', 'p' => 3 + end + assert_query_equal %w[a=a p=3], query + end + + def test_overrides_all_request_params + create_connection :params => {:c => 'c'} + query = get '?p=1&a=a', :p => 2 do |req| + assert_equal 'a', req.params[:a] + assert_equal 'c', req.params['c'] + assert_equal 2, req.params['p'] + req.params = {:b => 'b'} + assert_equal 'b', req.params['b'] + end + assert_query_equal %w[b=b], query + end + + def test_array_params_in_url + with_default_params_encoder(nil) do + create_connection 'http://a.co/page1?color[]=red&color[]=blue' + query = get + assert_equal "color%5B%5D=red&color%5B%5D=blue", query + end + end + + def test_array_params_in_params + with_default_params_encoder(nil) do + create_connection 'http://a.co/page1', :params => {:color => ['red', 'blue']} + query = get + assert_equal "color%5B%5D=red&color%5B%5D=blue", query + end + end + + def test_array_params_in_url_with_flat_params + with_default_params_encoder(Faraday::FlatParamsEncoder) do + create_connection 'http://a.co/page1?color=red&color=blue' + query = get + assert_equal "color=red&color=blue", query + end + end + + def test_array_params_in_params_with_flat_params + with_default_params_encoder(Faraday::FlatParamsEncoder) do + create_connection 'http://a.co/page1', :params => {:color => ['red', 'blue']} + query = get + assert_equal "color=red&color=blue", query + end + end + + def get(*args) + env = @conn.get(*args) do |req| + yield(req) if block_given? + end + env[:url].query + end +end diff --git a/.gems/gems/faraday-0.9.0/test/env_test.rb b/.gems/gems/faraday-0.9.0/test/env_test.rb new file mode 100644 index 0000000..4cae21a --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/env_test.rb @@ -0,0 +1,210 @@ +require File.expand_path('../helper', __FILE__) + +class EnvTest < Faraday::TestCase + def setup + @conn = Faraday.new :url => 'http://sushi.com/api', + :headers => {'Mime-Version' => '1.0'}, + :request => {:oauth => {:consumer_key => 'anonymous'}} + + @conn.options.timeout = 3 + @conn.options.open_timeout = 5 + @conn.ssl.verify = false + @conn.proxy 'http://proxy.com' + end + + def test_request_create_stores_method + env = make_env(:get) + assert_equal :get, env.method + end + + def test_request_create_stores_uri + env = make_env do |req| + req.url 'foo.json', 'a' => 1 + end + assert_equal 'http://sushi.com/api/foo.json?a=1', env.url.to_s + end + + def test_request_create_stores_headers + env = make_env do |req| + req['Server'] = 'Faraday' + end + headers = env.request_headers + assert_equal '1.0', headers['mime-version'] + assert_equal 'Faraday', headers['server'] + end + + def test_request_create_stores_body + env = make_env do |req| + req.body = 'hi' + end + assert_equal 'hi', env.body + end + + def test_global_request_options + env = make_env + assert_equal 3, env.request.timeout + assert_equal 5, env.request.open_timeout + end + + def test_per_request_options + env = make_env do |req| + req.options.timeout = 10 + req.options.boundary = 'boo' + req.options.oauth[:consumer_secret] = 'xyz' + end + assert_equal 10, env.request.timeout + assert_equal 5, env.request.open_timeout + assert_equal 'boo', env.request.boundary + + oauth_expected = {:consumer_secret => 'xyz', :consumer_key => 'anonymous'} + assert_equal oauth_expected, env.request.oauth + end + + def test_request_create_stores_ssl_options + env = make_env + assert_equal false, env.ssl.verify + end + + def test_request_create_stores_proxy_options + env = make_env + assert_equal 'proxy.com', env.request.proxy.host + end + + private + + def make_env(method = :get, connection = @conn, &block) + request = connection.build_request(method, &block) + request.to_env(connection) + end +end + +class HeadersTest < Faraday::TestCase + def setup + @headers = Faraday::Utils::Headers.new + end + + def test_normalizes_different_capitalizations + @headers['Content-Type'] = 'application/json' + assert_equal ['Content-Type'], @headers.keys + assert_equal 'application/json', @headers['Content-Type'] + assert_equal 'application/json', @headers['CONTENT-TYPE'] + assert_equal 'application/json', @headers['content-type'] + assert @headers.include?('content-type') + + @headers['content-type'] = 'application/xml' + assert_equal ['Content-Type'], @headers.keys + assert_equal 'application/xml', @headers['Content-Type'] + assert_equal 'application/xml', @headers['CONTENT-TYPE'] + assert_equal 'application/xml', @headers['content-type'] + end + + def test_fetch_key + @headers['Content-Type'] = 'application/json' + block_called = false + assert_equal 'application/json', @headers.fetch('content-type') { block_called = true } + assert_equal 'application/json', @headers.fetch('Content-Type') + assert_equal 'application/json', @headers.fetch('CONTENT-TYPE') + assert_equal 'application/json', @headers.fetch(:content_type) + assert_equal false, block_called + + assert_equal 'default', @headers.fetch('invalid', 'default') + assert_equal false, @headers.fetch('invalid', false) + assert_nil @headers.fetch('invalid', nil) + + assert_equal 'Invalid key', @headers.fetch('Invalid') { |key| "#{key} key" } + + expected_error = defined?(KeyError) ? KeyError : IndexError + assert_raises(expected_error) { @headers.fetch('invalid') } + end + + def test_delete_key + @headers['Content-Type'] = 'application/json' + assert_equal 1, @headers.size + assert @headers.include?('content-type') + assert_equal 'application/json', @headers.delete('content-type') + assert_equal 0, @headers.size + assert !@headers.include?('content-type') + assert_equal nil, @headers.delete('content-type') + end + + def test_parse_response_headers_leaves_http_status_line_out + @headers.parse("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n") + assert_equal %w(Content-Type), @headers.keys + end + + def test_parse_response_headers_parses_lower_cased_header_name_and_value + @headers.parse("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n") + assert_equal 'text/html', @headers['content-type'] + end + + def test_parse_response_headers_parses_lower_cased_header_name_and_value_with_colon + @headers.parse("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n") + assert_equal 'http://sushi.com/', @headers['location'] + end + + def test_parse_response_headers_parses_blank_lines + @headers.parse("HTTP/1.1 200 OK\r\n\r\nContent-Type: text/html\r\n\r\n") + assert_equal 'text/html', @headers['content-type'] + end +end + +class ResponseTest < Faraday::TestCase + def setup + @env = Faraday::Env.from \ + :status => 404, :body => 'yikes', + :response_headers => {'Content-Type' => 'text/plain'} + @response = Faraday::Response.new @env + end + + def test_finished + assert @response.finished? + end + + def test_error_on_finish + assert_raises RuntimeError do + @response.finish({}) + end + end + + def test_not_success + assert !@response.success? + end + + def test_status + assert_equal 404, @response.status + end + + def test_body + assert_equal 'yikes', @response.body + end + + def test_headers + assert_equal 'text/plain', @response.headers['Content-Type'] + assert_equal 'text/plain', @response['content-type'] + end + + def test_apply_request + @response.apply_request :body => 'a=b', :method => :post + assert_equal 'yikes', @response.body + assert_equal :post, @response.env[:method] + end + + def test_marshal + @response = Faraday::Response.new + @response.on_complete { } + @response.finish @env.merge(:params => 'moo') + + loaded = Marshal.load Marshal.dump(@response) + assert_nil loaded.env[:params] + assert_equal %w[body response_headers status], loaded.env.keys.map { |k| k.to_s }.sort + end + + def test_hash + hash = @response.to_hash + assert_kind_of Hash, hash + assert_equal @env.to_hash, hash + assert_equal hash[:status], @response.status + assert_equal hash[:response_headers], @response.headers + assert_equal hash[:body], @response.body + end +end diff --git a/.gems/gems/faraday-0.9.0/test/helper.rb b/.gems/gems/faraday-0.9.0/test/helper.rb new file mode 100644 index 0000000..9d3dda8 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/helper.rb @@ -0,0 +1,81 @@ +require 'rubygems' # rubygems/version doesn't work by itself +require 'rubygems/version' # for simplecov-html +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] +SimpleCov.start do + add_filter '/bundle/' +end + +gem 'minitest' if defined? Bundler +require 'minitest/autorun' + +if ENV['LEFTRIGHT'] + begin + require 'leftright' + rescue LoadError + puts "Run `gem install leftright` to install leftright." + end +end + +require File.expand_path('../../lib/faraday', __FILE__) + +require 'stringio' +require 'uri' + +module Faraday + module LiveServerConfig + def live_server=(value) + @@live_server = case value + when /^http/ + URI(value) + when /./ + URI('http://127.0.0.1:4567') + end + end + + def live_server? + defined? @@live_server + end + + # Returns an object that responds to `host` and `port`. + def live_server + live_server? and @@live_server + end + end + + class TestCase < MiniTest::Test + extend LiveServerConfig + self.live_server = ENV['LIVE'] + + def test_default + assert true + end unless defined? ::MiniTest + + def capture_warnings + old, $stderr = $stderr, StringIO.new + begin + yield + $stderr.string + ensure + $stderr = old + end + end + + def self.jruby? + defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE + end + + def self.rbx? + defined? RUBY_ENGINE and 'rbx' == RUBY_ENGINE + end + + def self.ssl_mode? + ENV['SSL'] == 'yes' + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/live_server.rb b/.gems/gems/faraday-0.9.0/test/live_server.rb new file mode 100644 index 0000000..c106b29 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/live_server.rb @@ -0,0 +1,67 @@ +require 'sinatra/base' + +module Faraday +class LiveServer < Sinatra::Base + set :environment, :test + disable :logging + disable :protection + + [:get, :post, :put, :patch, :delete, :options].each do |method| + send(method, '/echo') do + kind = request.request_method.downcase + out = kind.dup + out << ' ?' << request.GET.inspect if request.GET.any? + out << ' ' << request.POST.inspect if request.POST.any? + + content_type 'text/plain' + return out + end + end + + get '/echo_header' do + header = "HTTP_#{params[:name].tr('-', '_').upcase}" + request.env.fetch(header) { 'NONE' } + end + + post '/file' do + if params[:uploaded_file].respond_to? :each_key + "file %s %s %d" % [ + params[:uploaded_file][:filename], + params[:uploaded_file][:type], + params[:uploaded_file][:tempfile].size + ] + else + status 400 + end + end + + get '/multi' do + [200, { 'Set-Cookie' => 'one, two' }, ''] + end + + get '/who-am-i' do + request.env['REMOTE_ADDR'] + end + + get '/slow' do + sleep 10 + [200, {}, 'ok'] + end + + get '/204' do + status 204 # no content + end + + get '/ssl' do + request.secure?.to_s + end + + error do |e| + "#{e.class}\n#{e.to_s}\n#{e.backtrace.join("\n")}" + end +end +end + +if $0 == __FILE__ + Faraday::LiveServer.run! +end diff --git a/.gems/gems/faraday-0.9.0/test/middleware/instrumentation_test.rb b/.gems/gems/faraday-0.9.0/test/middleware/instrumentation_test.rb new file mode 100644 index 0000000..364f13c --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/middleware/instrumentation_test.rb @@ -0,0 +1,88 @@ +require File.expand_path("../../helper", __FILE__) + +module Middleware + class InstrumentationTest < Faraday::TestCase + def setup + @instrumenter = FakeInstrumenter.new + end + + def test_default_name + assert_equal 'request.faraday', options.name + end + + def test_default_instrumenter + begin + instrumenter = options.instrumenter + rescue NameError => err + assert_match 'ActiveSupport', err.to_s + else + assert_equal ActiveSupport::Notifications, instrumenter + end + end + + def test_name + assert_equal 'booya', options(:name => 'booya').name + end + + def test_instrumenter + assert_equal :boom, options(:instrumenter => :boom).instrumenter + end + + def test_instrumentation_with_default_name + assert_equal 0, @instrumenter.instrumentations.size + + faraday = conn + res = faraday.get '/' + assert_equal 'ok', res.body + + assert_equal 1, @instrumenter.instrumentations.size + name, env = @instrumenter.instrumentations.first + assert_equal 'request.faraday', name + assert_equal '/', env[:url].path + end + + def test_instrumentation + assert_equal 0, @instrumenter.instrumentations.size + + faraday = conn :name => 'booya' + res = faraday.get '/' + assert_equal 'ok', res.body + + assert_equal 1, @instrumenter.instrumentations.size + name, env = @instrumenter.instrumentations.first + assert_equal 'booya', name + assert_equal '/', env[:url].path + end + + class FakeInstrumenter + attr_reader :instrumentations + + def initialize + @instrumentations = [] + end + + def instrument(name, env) + @instrumentations << [name, env] + yield + end + end + + def options(hash = nil) + Faraday::Request::Instrumentation::Options.from hash + end + + def conn(hash = nil) + hash ||= {} + hash[:instrumenter] = @instrumenter + + Faraday.new do |f| + f.request :instrumentation, hash + f.adapter :test do |stub| + stub.get '/' do + [200, {}, 'ok'] + end + end + end + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/middleware/retry_test.rb b/.gems/gems/faraday-0.9.0/test/middleware/retry_test.rb new file mode 100644 index 0000000..7c5a119 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/middleware/retry_test.rb @@ -0,0 +1,109 @@ +require File.expand_path("../../helper", __FILE__) + +module Middleware + class RetryTest < Faraday::TestCase + def setup + @times_called = 0 + end + + def conn(*retry_args) + Faraday.new do |b| + b.request :retry, *retry_args + b.adapter :test do |stub| + stub.post('/unstable') { + @times_called += 1 + @explode.call @times_called + } + end + end + end + + def test_unhandled_error + @explode = lambda {|n| raise "boom!" } + assert_raises(RuntimeError) { conn.post("/unstable") } + assert_equal 1, @times_called + end + + def test_handled_error + @explode = lambda {|n| raise Errno::ETIMEDOUT } + assert_raises(Errno::ETIMEDOUT) { conn.post("/unstable") } + assert_equal 3, @times_called + end + + def test_legacy_max_retries + @explode = lambda {|n| raise Errno::ETIMEDOUT } + assert_raises(Errno::ETIMEDOUT) { conn(1).post("/unstable") } + assert_equal 2, @times_called + end + + def test_new_max_retries + @explode = lambda {|n| raise Errno::ETIMEDOUT } + assert_raises(Errno::ETIMEDOUT) { conn(:max => 3).post("/unstable") } + assert_equal 4, @times_called + end + + def test_interval + @explode = lambda {|n| raise Errno::ETIMEDOUT } + started = Time.now + assert_raises(Errno::ETIMEDOUT) { + conn(:max => 2, :interval => 0.1).post("/unstable") + } + assert_in_delta 0.2, Time.now - started, 0.04 + end + + def test_calls_sleep_amount + explode_app = MiniTest::Mock.new + explode_app.expect(:call, nil, [{:body=>nil}]) + def explode_app.call(env) + raise Errno::ETIMEDOUT + end + + retry_middleware = Faraday::Request::Retry.new(explode_app) + class << retry_middleware + attr_accessor :sleep_amount_retries + + def sleep_amount(retries) + self.sleep_amount_retries.delete(retries) + 0 + end + end + retry_middleware.sleep_amount_retries = [2, 1] + + assert_raises(Errno::ETIMEDOUT) { + retry_middleware.call({}) + } + + assert_empty retry_middleware.sleep_amount_retries + end + + def test_exponential_backoff + middleware = Faraday::Request::Retry.new(nil, :max => 5, :interval => 0.1, :backoff_factor => 2) + assert_equal middleware.sleep_amount(5), 0.1 + assert_equal middleware.sleep_amount(4), 0.2 + assert_equal middleware.sleep_amount(3), 0.4 + end + + def test_random_additional_interval_amount + middleware = Faraday::Request::Retry.new(nil, :max => 2, :interval => 0.1, :interval_randomness => 1.0) + sleep_amount = middleware.sleep_amount(2) + assert_operator sleep_amount, :>=, 0.1 + assert_operator sleep_amount, :<=, 0.2 + middleware = Faraday::Request::Retry.new(nil, :max => 2, :interval => 0.1, :interval_randomness => 0.5) + sleep_amount = middleware.sleep_amount(2) + assert_operator sleep_amount, :>=, 0.1 + assert_operator sleep_amount, :<=, 0.15 + middleware = Faraday::Request::Retry.new(nil, :max => 2, :interval => 0.1, :interval_randomness => 0.25) + sleep_amount = middleware.sleep_amount(2) + assert_operator sleep_amount, :>=, 0.1 + assert_operator sleep_amount, :<=, 0.125 + end + + def test_custom_exceptions + @explode = lambda {|n| raise "boom!" } + assert_raises(RuntimeError) { + conn(:exceptions => StandardError).post("/unstable") + } + assert_equal 3, @times_called + end + end +end diff --git a/.gems/gems/faraday-0.9.0/test/middleware_stack_test.rb b/.gems/gems/faraday-0.9.0/test/middleware_stack_test.rb new file mode 100644 index 0000000..2f0d0d3 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/middleware_stack_test.rb @@ -0,0 +1,173 @@ +require File.expand_path('../helper', __FILE__) + +class MiddlewareStackTest < Faraday::TestCase + # mock handler classes + class Handler < Struct.new(:app) + def call(env) + (env[:request_headers]['X-Middleware'] ||= '') << ":#{self.class.name.split('::').last}" + app.call(env) + end + end + class Apple < Handler; end + class Orange < Handler; end + class Banana < Handler; end + + class Broken < Faraday::Middleware + dependency 'zomg/i_dont/exist' + end + + def setup + @conn = Faraday::Connection.new + @builder = @conn.builder + end + + def test_sets_default_adapter_if_none_set + default_middleware = Faraday::Request.lookup_middleware :url_encoded + default_adapter_klass = Faraday::Adapter.lookup_middleware Faraday.default_adapter + assert @builder[0] == default_middleware + assert @builder[1] == default_adapter_klass + end + + def test_allows_rebuilding + build_stack Apple + build_stack Orange + assert_handlers %w[Orange] + end + + def test_allows_extending + build_stack Apple + @conn.use Orange + assert_handlers %w[Apple Orange] + end + + def test_builder_is_passed_to_new_faraday_connection + new_conn = Faraday::Connection.new :builder => @builder + assert_equal @builder, new_conn.builder + end + + def test_insert_before + build_stack Apple, Orange + @builder.insert_before Apple, Banana + assert_handlers %w[Banana Apple Orange] + end + + def test_insert_after + build_stack Apple, Orange + @builder.insert_after Apple, Banana + assert_handlers %w[Apple Banana Orange] + end + + def test_swap_handlers + build_stack Apple, Orange + @builder.swap Apple, Banana + assert_handlers %w[Banana Orange] + end + + def test_delete_handler + build_stack Apple, Orange + @builder.delete Apple + assert_handlers %w[Orange] + end + + def test_stack_is_locked_after_making_requests + build_stack Apple + assert !@builder.locked? + @conn.get('/') + assert @builder.locked? + + assert_raises Faraday::RackBuilder::StackLocked do + @conn.use Orange + end + end + + def test_duped_stack_is_unlocked + build_stack Apple + assert !@builder.locked? + @builder.lock! + assert @builder.locked? + + duped_connection = @conn.dup + assert_equal @builder, duped_connection.builder + assert !duped_connection.builder.locked? + end + + def test_handler_comparison + build_stack Apple + assert_equal @builder.handlers.first, Apple + assert_equal @builder.handlers[0,1], [Apple] + assert_equal @builder.handlers.first, Faraday::RackBuilder::Handler.new(Apple) + end + + def test_unregistered_symbol + err = assert_raises(Faraday::Error){ build_stack :apple } + assert_equal ":apple is not registered on Faraday::Middleware", err.message + end + + def test_registered_symbol + Faraday::Middleware.register_middleware :apple => Apple + begin + build_stack :apple + assert_handlers %w[Apple] + ensure + unregister_middleware Faraday::Middleware, :apple + end + end + + def test_registered_symbol_with_proc + Faraday::Middleware.register_middleware :apple => lambda { Apple } + begin + build_stack :apple + assert_handlers %w[Apple] + ensure + unregister_middleware Faraday::Middleware, :apple + end + end + + def test_registered_symbol_with_array + Faraday::Middleware.register_middleware File.expand_path("..", __FILE__), + :strawberry => [lambda { Strawberry }, 'strawberry'] + begin + build_stack :strawberry + assert_handlers %w[Strawberry] + ensure + unregister_middleware Faraday::Middleware, :strawberry + end + end + + def test_missing_dependencies + build_stack Broken + err = assert_raises RuntimeError do + @conn.get('/') + end + assert_match "missing dependency for MiddlewareStackTest::Broken: ", err.message + assert_match "zomg/i_dont/exist", err.message + end + + private + + # make a stack with test adapter that reflects the order of middleware + def build_stack(*handlers) + @builder.build do |b| + handlers.each { |handler| b.use(*handler) } + yield(b) if block_given? + + b.adapter :test do |stub| + stub.get '/' do |env| + # echo the "X-Middleware" request header in the body + [200, {}, env[:request_headers]['X-Middleware'].to_s] + end + end + end + end + + def assert_handlers(list) + echoed_list = @conn.get('/').body.to_s.split(':') + echoed_list.shift if echoed_list.first == '' + assert_equal list, echoed_list + end + + def unregister_middleware(component, key) + # TODO: unregister API? + component.instance_variable_get('@registered_middleware').delete(key) + end +end diff --git a/.gems/gems/faraday-0.9.0/test/multibyte.txt b/.gems/gems/faraday-0.9.0/test/multibyte.txt new file mode 100644 index 0000000..24a84b0 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/multibyte.txt @@ -0,0 +1 @@ +ファイル diff --git a/.gems/gems/faraday-0.9.0/test/options_test.rb b/.gems/gems/faraday-0.9.0/test/options_test.rb new file mode 100644 index 0000000..1cacdf6 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/options_test.rb @@ -0,0 +1,252 @@ +require File.expand_path('../helper', __FILE__) + +class OptionsTest < Faraday::TestCase + class SubOptions < Faraday::Options.new(:sub); end + class ParentOptions < Faraday::Options.new(:a, :b, :c) + options :c => SubOptions + end + + def test_clear + options = SubOptions.new(1) + assert !options.empty? + assert options.clear + assert options.empty? + end + + def test_empty + options = SubOptions.new + assert options.empty? + options.sub = 1 + assert !options.empty? + options.delete(:sub) + assert options.empty? + end + + def test_each_key + options = ParentOptions.new(1, 2, 3) + enum = options.each_key + assert_equal enum.next.to_sym, :a + assert_equal enum.next.to_sym, :b + assert_equal enum.next.to_sym, :c + end + + def test_key? + options = SubOptions.new + assert !options.key?(:sub) + options.sub = 1 + if RUBY_VERSION >= '1.9' + assert options.key?(:sub) + else + assert options.key?("sub") + end + end + + def test_each_value + options = ParentOptions.new(1, 2, 3) + enum = options.each_value + assert_equal enum.next, 1 + assert_equal enum.next, 2 + assert_equal enum.next, 3 + end + + def test_value? + options = SubOptions.new + assert !options.value?(1) + options.sub = 1 + assert options.value?(1) + end + + def test_request_proxy_setter + options = Faraday::RequestOptions.new + assert_nil options.proxy + + assert_raises NoMethodError do + options[:proxy] = {:booya => 1} + end + + options[:proxy] = {:user => 'user'} + assert_kind_of Faraday::ProxyOptions, options.proxy + assert_equal 'user', options.proxy.user + + options.proxy = nil + assert_nil options.proxy + end + + def test_proxy_options_from_string + options = Faraday::ProxyOptions.from 'http://user:pass@example.org' + assert_equal 'user', options.user + assert_equal 'pass', options.password + assert_kind_of URI, options.uri + assert_equal '', options.path + assert_equal 80, options.port + assert_equal 'example.org', options.host + assert_equal 'http', options.scheme + end + + def test_proxy_options_hash_access + proxy = Faraday::ProxyOptions.from 'http://a%40b:pw%20d@example.org' + assert_equal 'a@b', proxy[:user] + assert_equal 'a@b', proxy.user + assert_equal 'pw d', proxy[:password] + assert_equal 'pw d', proxy.password + end + + def test_proxy_options_no_auth + proxy = Faraday::ProxyOptions.from 'http://example.org' + assert_nil proxy.user + assert_nil proxy.password + end + + def test_from_options + options = ParentOptions.new(1) + + value = ParentOptions.from(options) + assert_equal 1, value.a + assert_nil value.b + end + + def test_from_options_with_sub_object + sub = SubOptions.new(1) + options = ParentOptions.from :a => 1, :c => sub + assert_kind_of ParentOptions, options + assert_equal 1, options.a + assert_nil options.b + assert_kind_of SubOptions, options.c + assert_equal 1, options.c.sub + end + + def test_from_hash + options = ParentOptions.from :a => 1 + assert_kind_of ParentOptions, options + assert_equal 1, options.a + assert_nil options.b + end + + def test_from_hash_with_sub_object + options = ParentOptions.from :a => 1, :c => {:sub => 1} + assert_kind_of ParentOptions, options + assert_equal 1, options.a + assert_nil options.b + assert_kind_of SubOptions, options.c + assert_equal 1, options.c.sub + end + + def test_inheritance + subclass = Class.new(ParentOptions) + options = subclass.from(:c => {:sub => 'hello'}) + assert_kind_of SubOptions, options.c + assert_equal 'hello', options.c.sub + end + + def test_from_deep_hash + hash = {:b => 1} + options = ParentOptions.from :a => hash + assert_equal 1, options.a[:b] + + hash[:b] = 2 + assert_equal 1, options.a[:b] + + options.a[:b] = 3 + assert_equal 2, hash[:b] + assert_equal 3, options.a[:b] + end + + def test_from_nil + options = ParentOptions.from(nil) + assert_kind_of ParentOptions, options + assert_nil options.a + assert_nil options.b + end + + def test_invalid_key + assert_raises NoMethodError do + ParentOptions.from :invalid => 1 + end + end + + def test_update + options = ParentOptions.new(1) + assert_equal 1, options.a + assert_nil options.b + + updated = options.update :a => 2, :b => 3 + assert_equal 2, options.a + assert_equal 3, options.b + assert_equal options, updated + end + + def test_delete + options = ParentOptions.new(1) + assert_equal 1, options.a + assert_equal 1, options.delete(:a) + assert_nil options.a + end + + def test_merge + options = ParentOptions.new(1) + assert_equal 1, options.a + assert_nil options.b + + dup = options.merge :a => 2, :b => 3 + assert_equal 2, dup.a + assert_equal 3, dup.b + assert_equal 1, options.a + assert_nil options.b + end + + def test_env_access_member + e = Faraday::Env.new + assert_nil e.method + e.method = :get + assert_equal :get, e.method + end + + def test_env_access_symbol_non_member + e = Faraday::Env.new + assert_nil e[:custom] + e[:custom] = :boom + assert_equal :boom, e[:custom] + end + + def test_env_access_string_non_member + e = Faraday::Env.new + assert_nil e["custom"] + e["custom"] = :boom + assert_equal :boom, e["custom"] + end + + def test_env_fetch_ignores_false + ssl = Faraday::SSLOptions.new + ssl.verify = false + assert !ssl.fetch(:verify, true) + end + + def test_fetch_grabs_value + opt = Faraday::SSLOptions.new + opt.verify = 1 + assert_equal 1, opt.fetch(:verify, false) { |k| :blah } + end + + def test_fetch_uses_falsey_default + opt = Faraday::SSLOptions.new + assert_equal false, opt.fetch(:verify, false) { |k| :blah } + end + + def test_fetch_accepts_block + opt = Faraday::SSLOptions.new + assert_equal "yo :verify", opt.fetch(:verify) { |k| "yo #{k.inspect}"} + end + + def test_fetch_needs_a_default_if_key_is_missing + opt = Faraday::SSLOptions.new + assert_raises Faraday::Options.fetch_error_class do + opt.fetch :verify + end + end + + def test_fetch_works_with_key + opt = Faraday::SSLOptions.new + opt.verify = 1 + assert_equal 1, opt.fetch(:verify) + end +end diff --git a/.gems/gems/faraday-0.9.0/test/request_middleware_test.rb b/.gems/gems/faraday-0.9.0/test/request_middleware_test.rb new file mode 100644 index 0000000..aca011f --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/request_middleware_test.rb @@ -0,0 +1,142 @@ +# encoding: utf-8 +require File.expand_path('../helper', __FILE__) + +Faraday::CompositeReadIO.class_eval { attr_reader :ios } + +class RequestMiddlewareTest < Faraday::TestCase + def setup + @conn = Faraday.new do |b| + b.request :multipart + b.request :url_encoded + b.adapter :test do |stub| + stub.post('/echo') do |env| + posted_as = env[:request_headers]['Content-Type'] + [200, {'Content-Type' => posted_as}, env[:body]] + end + end + end + end + + def with_utf8 + if defined?(RUBY_VERSION) && RUBY_VERSION.match(/1.8.\d/) + begin + previous_kcode = $KCODE + $KCODE = "UTF8" + yield + ensure + $KCODE = previous_kcode + end + else + yield + end + end + + def test_does_nothing_without_payload + response = @conn.post('/echo') + assert_nil response.headers['Content-Type'] + assert response.body.empty? + end + + def test_ignores_custom_content_type + response = @conn.post('/echo', { :some => 'data' }, 'content-type' => 'application/x-foo') + assert_equal 'application/x-foo', response.headers['Content-Type'] + assert_equal({ :some => 'data' }, response.body) + end + + def test_url_encoded_no_header + response = @conn.post('/echo', { :fruit => %w[apples oranges] }) + assert_equal 'application/x-www-form-urlencoded', response.headers['Content-Type'] + assert_equal 'fruit%5B%5D=apples&fruit%5B%5D=oranges', response.body + end + + def test_url_encoded_with_header + response = @conn.post('/echo', {'a'=>123}, 'content-type' => 'application/x-www-form-urlencoded') + assert_equal 'application/x-www-form-urlencoded', response.headers['Content-Type'] + assert_equal 'a=123', response.body + end + + def test_url_encoded_nested + response = @conn.post('/echo', { :user => {:name => 'Mislav', :web => 'mislav.net'} }) + assert_equal 'application/x-www-form-urlencoded', response.headers['Content-Type'] + expected = { 'user' => {'name' => 'Mislav', 'web' => 'mislav.net'} } + assert_equal expected, Faraday::Utils.parse_nested_query(response.body) + end + + def test_url_encoded_non_nested + response = @conn.post('/echo', { :dimensions => ['date', 'location']}) do |req| + req.options.params_encoder = Faraday::FlatParamsEncoder + end + assert_equal 'application/x-www-form-urlencoded', response.headers['Content-Type'] + expected = { 'dimensions' => ['date', 'location'] } + assert_equal expected, Faraday::Utils.parse_query(response.body) + assert_equal 'dimensions=date&dimensions=location', response.body + end + + def test_url_encoded_unicode + err = capture_warnings { + response = @conn.post('/echo', {:str => "eé cç aã aâ"}) + assert_equal "str=e%C3%A9+c%C3%A7+a%C3%A3+a%C3%A2", response.body + } + assert err.empty?, "stderr did include: #{err}" + end + + def test_url_encoded_unicode_with_kcode_set + with_utf8 do + err = capture_warnings { + response = @conn.post('/echo', {:str => "eé cç aã aâ"}) + assert_equal "str=e%C3%A9+c%C3%A7+a%C3%A3+a%C3%A2", response.body + } + assert err.empty?, "stderr did include: #{err}" + end + end + + def test_url_encoded_nested_keys + response = @conn.post('/echo', {'a'=>{'b'=>{'c'=>['d']}}}) + assert_equal "a%5Bb%5D%5Bc%5D%5B%5D=d", response.body + end + + def test_multipart + # assume params are out of order + regexes = [ + /name\=\"a\"/, + /name=\"b\[c\]\"\; filename\=\"request_middleware_test\.rb\"/, + /name=\"b\[d\]\"/] + + payload = {:a => 1, :b => {:c => Faraday::UploadIO.new(__FILE__, 'text/x-ruby'), :d => 2}} + response = @conn.post('/echo', payload) + + assert_kind_of Faraday::CompositeReadIO, response.body + assert_equal "multipart/form-data; boundary=%s" % Faraday::Request::Multipart::DEFAULT_BOUNDARY, + response.headers['Content-Type'] + + response.body.send(:ios).map{|io| io.read}.each do |io| + if re = regexes.detect { |r| io =~ r } + regexes.delete re + end + end + assert_equal [], regexes + end + + def test_multipart_with_arrays + # assume params are out of order + regexes = [ + /name\=\"a\"/, + /name=\"b\[\]\[c\]\"\; filename\=\"request_middleware_test\.rb\"/, + /name=\"b\[\]\[d\]\"/] + + payload = {:a => 1, :b =>[{:c => Faraday::UploadIO.new(__FILE__, 'text/x-ruby'), :d => 2}]} + response = @conn.post('/echo', payload) + + assert_kind_of Faraday::CompositeReadIO, response.body + assert_equal "multipart/form-data; boundary=%s" % Faraday::Request::Multipart::DEFAULT_BOUNDARY, + response.headers['Content-Type'] + + response.body.send(:ios).map{|io| io.read}.each do |io| + if re = regexes.detect { |r| io =~ r } + regexes.delete re + end + end + assert_equal [], regexes + end + +end diff --git a/.gems/gems/faraday-0.9.0/test/response_middleware_test.rb b/.gems/gems/faraday-0.9.0/test/response_middleware_test.rb new file mode 100644 index 0000000..ea8aba2 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/response_middleware_test.rb @@ -0,0 +1,72 @@ +require File.expand_path('../helper', __FILE__) + +class ResponseMiddlewareTest < Faraday::TestCase + def setup + @conn = Faraday.new do |b| + b.response :raise_error + b.adapter :test do |stub| + stub.get('ok') { [200, {'Content-Type' => 'text/html'}, ''] } + stub.get('not-found') { [404, {'X-Reason' => 'because'}, 'keep looking'] } + stub.get('error') { [500, {'X-Error' => 'bailout'}, 'fail'] } + end + end + end + + class ResponseUpcaser < Faraday::Response::Middleware + def parse(body) + body.upcase + end + end + + def test_success + assert @conn.get('ok') + end + + def test_raises_not_found + error = assert_raises Faraday::Error::ResourceNotFound do + @conn.get('not-found') + end + assert_equal 'the server responded with status 404', error.message + assert_equal 'because', error.response[:headers]['X-Reason'] + end + + def test_raises_error + error = assert_raises Faraday::Error::ClientError do + @conn.get('error') + end + assert_equal 'the server responded with status 500', error.message + assert_equal 'bailout', error.response[:headers]['X-Error'] + end + + def test_upcase + @conn.builder.insert(0, ResponseUpcaser) + assert_equal '', @conn.get('ok').body + end +end + +class ResponseNoBodyMiddleWareTest < Faraday::TestCase + def setup + @conn = Faraday.new do |b| + b.response :raise_error + b.adapter :test do |stub| + stub.get('not_modified') { [304, nil, nil] } + stub.get('no_content') { [204, nil, nil] } + end + end + @conn.builder.insert(0, NotCalled) + end + + class NotCalled < Faraday::Response::Middleware + def parse(body) + raise "this should not be called" + end + end + + def test_204 + assert_equal nil, @conn.get('no_content').body + end + + def test_304 + assert_equal nil, @conn.get('not_modified').body + end +end diff --git a/.gems/gems/faraday-0.9.0/test/strawberry.rb b/.gems/gems/faraday-0.9.0/test/strawberry.rb new file mode 100644 index 0000000..80c8d0b --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/strawberry.rb @@ -0,0 +1,2 @@ +class MiddlewareStackTest::Strawberry < MiddlewareStackTest::Handler +end diff --git a/.gems/gems/faraday-0.9.0/test/utils_test.rb b/.gems/gems/faraday-0.9.0/test/utils_test.rb new file mode 100644 index 0000000..b54f208 --- /dev/null +++ b/.gems/gems/faraday-0.9.0/test/utils_test.rb @@ -0,0 +1,58 @@ +require File.expand_path('../helper', __FILE__) + +class TestUtils < Faraday::TestCase + def setup + @url = "http://example.com/abc" + end + + # emulates ActiveSupport::SafeBuffer#gsub + FakeSafeBuffer = Struct.new(:string) do + def to_s() self end + def gsub(regex) + string.gsub(regex) { + match, = $&, '' =~ /a/ + yield(match) + } + end + end + + def test_escaping_safe_buffer + str = FakeSafeBuffer.new('$32,000.00') + assert_equal '%2432%2C000.00', Faraday::Utils.escape(str) + end + + def test_parses_with_default + with_default_uri_parser(nil) do + uri = normalize(@url) + assert_equal 'example.com', uri.host + end + end + + def test_parses_with_URI + with_default_uri_parser(::URI) do + uri = normalize(@url) + assert_equal 'example.com', uri.host + end + end + + def test_parses_with_block + with_default_uri_parser(lambda {|u| "booya#{"!" * u.size}" }) do + assert_equal 'booya!!!!!!!!!!!!!!!!!!!!!!', normalize(@url) + end + end + + def normalize(url) + Faraday::Utils::URI(url) + end + + def with_default_uri_parser(parser) + old_parser = Faraday::Utils.default_uri_parser + begin + Faraday::Utils.default_uri_parser = parser + yield + ensure + Faraday::Utils.default_uri_parser = old_parser + end + end +end + diff --git a/.gems/gems/http-0.6.2/.coveralls.yml b/.gems/gems/http-0.6.2/.coveralls.yml new file mode 100644 index 0000000..e1da6a3 --- /dev/null +++ b/.gems/gems/http-0.6.2/.coveralls.yml @@ -0,0 +1 @@ +service-name: travis-pro diff --git a/.gems/gems/http-0.6.2/.gitignore b/.gems/gems/http-0.6.2/.gitignore new file mode 100644 index 0000000..79ed009 --- /dev/null +++ b/.gems/gems/http-0.6.2/.gitignore @@ -0,0 +1,18 @@ +*.gem +.bundle +.config +.rvmrc +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc +lib/bundler/man +measurement +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/.gems/gems/http-0.6.2/.rspec b/.gems/gems/http-0.6.2/.rspec new file mode 100644 index 0000000..65d1e74 --- /dev/null +++ b/.gems/gems/http-0.6.2/.rspec @@ -0,0 +1,5 @@ +--backtrace +--color +--format=documentation +--order random +--warnings diff --git a/.gems/gems/http-0.6.2/.rubocop.yml b/.gems/gems/http-0.6.2/.rubocop.yml new file mode 100644 index 0000000..8aae49f --- /dev/null +++ b/.gems/gems/http-0.6.2/.rubocop.yml @@ -0,0 +1,116 @@ +AllCops: + Include: + - 'Gemfile' + - 'Rakefile' + - 'http.gemspec' + +# Avoid long parameter lists +ParameterLists: + Max: 3 + CountKeywordArgs: true + +MethodLength: + CountComments: false + Max: 31 # TODO: lower to 15 + +ClassLength: + CountComments: false + Max: 100 + +CyclomaticComplexity: + Max: 13 # TODO: lower to 6 + +# Avoid more than `Max` levels of nesting. +BlockNesting: + Max: 3 + +# Do not force public/protected/private keyword to be indented at the same +# level as the def keyword. My personal preference is to outdent these keywords +# because I think when scanning code it makes it easier to identify the +# sections of code and visually separate them. When the keyword is at the same +# level I think it sort of blends in with the def keywords and makes it harder +# to scan the code and see where the sections are. +AccessModifierIndentation: + Enabled: false + +# Limit line length +LineLength: + Enabled: false + +# Disable documentation checking until a class needs to be documented once +Documentation: + Enabled: false + +# Not all trivial readers/writers can be defined with attr_* methods +TrivialAccessors: + Enabled: false + +# Enforce Ruby 1.8-compatible hash syntax +HashSyntax: + EnforcedStyle: hash_rockets + +# No spaces inside hash literals +SpaceInsideHashLiteralBraces: + EnforcedStyle: no_space + +# Allow dots at the end of lines +DotPosition: + Enabled: false + +# Don't require magic comment at the top of every file +Encoding: + Enabled: false + +# Enforce outdenting of access modifiers (i.e. public, private, protected) +AccessModifierIndentation: + EnforcedStyle: outdent + +EmptyLinesAroundAccessModifier: + Enabled: true + +# Align ends correctly +EndAlignment: + AlignWith: variable + +# Indentation of when/else +CaseIndentation: + IndentWhenRelativeTo: end + IndentOneStep: false + +# Use the old lambda literal syntax +Lambda: + Enabled: false + +DoubleNegation: + Enabled: false + +PercentLiteralDelimiters: + PreferredDelimiters: + '%': () + '%i': () + '%q': () + '%Q': () + '%r': '{}' + '%s': () + '%w': '[]' + '%W': '[]' + '%x': () + +Semicolon: + Exclude: + - 'spec/support/' + +# Do not force first argument to be separated with exactly single space. +# My (ixti) personal preference is to align code in columns when it makes +# sense: +# +# module HTTP +# module MimeType +# class JSON < Adapter +# register_adapter 'application/json', JSON +# register_alias 'application/json', :json +# end +# end +# end +SingleSpaceBeforeFirstArg: + Enabled: false diff --git a/.gems/gems/http-0.6.2/.travis.yml b/.gems/gems/http-0.6.2/.travis.yml new file mode 100644 index 0000000..b24aba1 --- /dev/null +++ b/.gems/gems/http-0.6.2/.travis.yml @@ -0,0 +1,21 @@ +bundler_args: --without development +env: + global: + - JRUBY_OPTS="$JRUBY_OPTS --debug" +language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0.0 + - 2.1 + - jruby-18mode + - jruby-19mode + - jruby-head + - rbx-2 + - ruby-head +matrix: + allow_failures: + - rvm: jruby-head + - rvm: ruby-head + fast_finish: true diff --git a/.gems/gems/http-0.6.2/CHANGES.md b/.gems/gems/http-0.6.2/CHANGES.md new file mode 100644 index 0000000..d0f39e8 --- /dev/null +++ b/.gems/gems/http-0.6.2/CHANGES.md @@ -0,0 +1,132 @@ +0.6.2 (2014-08-06) +------------------ + +* Fix default Host header value. See #150. (@ixti) +* Deprecate BearerToken authorization header. (@ixti) +* Fix handling of chunked responses without Content-Length header. (@ixti) +* Rename `HTTP.with_follow` to `HTTP.follow` and mark former one as being + deprecated (@ixti) + +0.6.1 (2014-05-07) +------------------ + +* Fix request `Content-Length` calculation for Unicode (@challengeechallengee) +* Add `Response#flush` (@ixti) +* Fix `Response::Body#readpartial` default size (@hannesg, @ixti) +* Add missing `CRLF` for chunked bodies (@hannesg) +* Fix forgotten CGI require (@ixti) +* Improve README (@tarcieri) + +0.6.0 (2014-04-04) +------------------ + +* Rename `HTTP::Request#method` to `HTTP::Request#verb` (@krainboltgreene) +* Add `HTTP::ResponseBody` class (@tarcieri) +* Change API of response on `HTTP::Client.request` and "friends" (`#get`, `#post`, etc) (@tarcieri) +* Add `HTTP::Response#readpartial` (@tarcieri) +* Add `HTTP::Headers` class (@ixti) +* Fix and improve following redirects (@ixti) +* Add `HTTP::Request#redirect` (@ixti) +* Add `HTTP::Response#content_type` (@ixti) +* Add `HTTP::Response#mime_type` (@ixti) +* Add `HTTP::Response#charset` (@ixti) +* Improve error message upon invalid URI scheme (@ixti) +* Consolidate errors under common `HTTP::Error` namespace (@ixti) +* Add easy way of adding Authorization header (@ixti) +* Fix proxy support (@hundredwatt) +* Fix and improve query params handing (@jwinter) +* Change API of custom MIME type parsers (@ixti) +* Remove `HTTP::Chainable#with_response` (@ixti) +* Remove `HTTP::Response::BodyDelegator` (@ixti) +* Remove `HTTP::Response#parsed_body` (@ixti) +* Bump up input buffer from 4K to 16K (@tarcieri) + +``` ruby +# Main API change you will mention is that `request` method and it's +# syntax sugar helpers like `get`, `post`, etc. now returns Response +# object instead of BodyDelegator: + +response = HTTP.get "http://example.com" +raw_body = HTTP.get("http://example.com").to_s +parsed_body = HTTP.get("http://example.com/users.json").parse + +# Second major change in API is work with request/response headers +# It is now delegated to `HTTP::Headers` class, so you can check it's +# documentation for details, here we will only outline main difference. +# Duckface (`[]=`) does not appends headers anymore + +request[:content_type] = "text/plain" +request[:content_type] = "text/html" +request[:content_type] # => "text/html" + +# In order to add multiple header values, you should pass array: + +request[:cookie] = ["foo=bar", "woo=hoo"] +request[:cookie] # => ["foo=bar", "woo=hoo"] + +# or call `#add` on headers: + +request.headers.add :accept, "text/plain" +request.headers.add :accept, "text/html" +request[:accept] # => ["text/plain", "text/html"] + +# Also, you can now read body in chunks (stream): + +res = HTTP.get "http://example.com" +File.open "/tmp/dummy.bin", "wb" do |io| + while (chunk = res.readpartial) + io << chunk + end +end +``` + +[Changes discussion](https://github.com/tarcieri/http/issues/116) + +0.5.1 (2014-05-27) +------------------ + +* Backports redirector fixes from 0.6.0 (@ixti) +* EOL of 0.5.X branch. + +0.5.0 +----- +* Add query string support +* New response delegator allows HTTP.get(uri).response +* HTTP::Chainable#stream provides a shorter alias for + with_response(:object) +* Better string inspect for HTTP::Response +* Curb compatibility layer removed + +0.4.0 +----- +* Fix bug accessing https URLs +* Fix several instances of broken redirect handling +* Add default user agent +* Many additional minor bugfixes + +0.3.0 +----- +* New implementation based on tmm1's http_parser.rb instead of Net::HTTP +* Support for following redirects +* Support for request body through {:body => ...} option +* HTTP#with_response (through Chainable) + +0.2.0 +----- +* Request and response objects +* Callback system +* Internal refactoring ensuring true chainability +* Use the certified gem to ensure SSL certificate verification + +0.1.0 +----- +* Testing against WEBrick +* Curb compatibility (require 'http/compat/curb') + +0.0.1 +----- +* Initial half-baked release + +0.0.0 +----- +* Vapoware release to claim the "http" gem name >:D diff --git a/.gems/gems/http-0.6.2/Gemfile b/.gems/gems/http-0.6.2/Gemfile new file mode 100644 index 0000000..0cace9c --- /dev/null +++ b/.gems/gems/http-0.6.2/Gemfile @@ -0,0 +1,31 @@ +source 'http://rubygems.org' + +gem 'rake', '~> 10.1.1' +gem 'jruby-openssl' if defined? JRUBY_VERSION + +group :development do + gem 'pry' + platforms :ruby_19, :ruby_20 do + gem 'pry-debugger' + gem 'pry-stack_explorer' + end + platforms :ruby_19, :ruby_20, :ruby_21 do + gem 'celluloid-io' + gem 'guard-rspec' + end +end + +group :test do + gem 'backports' + gem 'coveralls' + gem 'json', '>= 1.8.1', :platforms => [:jruby, :rbx, :ruby_18, :ruby_19] + gem 'mime-types', '~> 1.25', :platforms => [:jruby, :ruby_18] + gem 'rest-client', '~> 1.6.0', :platforms => [:jruby, :ruby_18] + gem 'rspec', '~> 2.14' + gem 'rubocop', '~> 0.24.0', :platforms => [:ruby_19, :ruby_20, :ruby_21] + gem 'simplecov', '>= 0.9' + gem 'yardstick' +end + +# Specify your gem's dependencies in http.gemspec +gemspec diff --git a/.gems/gems/http-0.6.2/Guardfile b/.gems/gems/http-0.6.2/Guardfile new file mode 100644 index 0000000..2f00b11 --- /dev/null +++ b/.gems/gems/http-0.6.2/Guardfile @@ -0,0 +1,9 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :rspec do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + diff --git a/.gems/gems/http-0.6.2/LICENSE.txt b/.gems/gems/http-0.6.2/LICENSE.txt new file mode 100644 index 0000000..92021d1 --- /dev/null +++ b/.gems/gems/http-0.6.2/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2014 Tony Arcieri, Erik Michaels-Ober + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/http-0.6.2/README.md b/.gems/gems/http-0.6.2/README.md new file mode 100644 index 0000000..fac2090 --- /dev/null +++ b/.gems/gems/http-0.6.2/README.md @@ -0,0 +1,259 @@ +![The HTTP Gem](https://raw.github.com/tarcieri/http/master/logo.png) +============== +[![Gem Version](https://badge.fury.io/rb/http.png)](http://rubygems.org/gems/http) +[![Build Status](https://secure.travis-ci.org/tarcieri/http.png?branch=master)](http://travis-ci.org/tarcieri/http) +[![Code Climate](https://codeclimate.com/github/tarcieri/http.png)](https://codeclimate.com/github/tarcieri/http) +[![Coverage Status](https://coveralls.io/repos/tarcieri/http/badge.png?branch=master)](https://coveralls.io/r/tarcieri/http) + +SEO Note +-------- + +This Gem has the worst name in the history of SEO. But perhaps we can fix that if we +all refer to it as "The HTTP Gem", or even better, the "Ruby HTTP Gem". + +About +----- + +The HTTP Gem is an easy-to-use client library for making requests from Ruby. It uses +a simple method chaining system for building requests, similar to Python's [Requests] + +Under the hood, The HTTP Gem uses [http_parser.rb], a fast HTTP parsing native +extension based on the Node.js parser and a Java port thereof. + +[requests]: http://docs.python-requests.org/en/latest/ +[http_parser.rb]: https://github.com/tmm1/http_parser.rb + +Help and Discussion +------------------- + +If you need help or just want to talk about the Ruby HTTP Gem, [visit our Google +Group][googlegroup], or join by email by sending a message to: +[ruby-http-gem+subscribe@googlegroups.com][subscribe]. + +[googlegroup]: https://groups.google.com/forum/#!forum/ruby-http-gem +[subscribe]: mailto:ruby-http-gem+subscribe@googlegroups.com + +If you believe you've found a bug, please report it at: + +https://github.com/tarcieri/http/issues + +Installation +------------ + +Add this line to your application's Gemfile: + + gem 'http' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install http + +Inside of your Ruby program do: + + require 'http' + +...to pull it in as a dependency. + +Documentation +------------- + +[Please see the HTTP Gem Wiki](https://github.com/tarcieri/http/wiki) +for more detailed documentation and usage notes. + +Basic Usage +----------- + +Here's some simple examples to get you started: + +### GET requests + +```ruby +>> HTTP.get("http://www.google.com").to_s +=> "> HTTP.get("http://www.google.com") +=> #"text/html; charset=UTF-8", "Date"=>"Fri, ...> + => #"text/html; ...> +``` + +We can also obtain an `HTTP::ResponseBody` object for this response: + +```ruby +>> HTTP.get("http://www.google.com").body + => # +``` + +The response body can be streamed with `HTTP::ResponseBody#readpartial`: + +```ruby +>> HTTP.get("http://www.google.com").body.readpartial + => " {:foo => "42"} +``` +Making GET requests with query string parameters is as simple. + +```ruby +HTTP.get "http://example.com/resource", :params => {:foo => "bar"} +``` + +Want to POST with a specific body, JSON for instance? + +```ruby +HTTP.post "http://example.com/resource", :json => { :foo => '42' } +``` + +It's easy! + + +### Proxy Support + +Making request behind proxy is as simple as making them directly. Just specify +hostname (or IP address) of your proxy server and it's port, and here you go: + +```ruby +HTTP.via("proxy-hostname.local", 8080) + .get "http://example.com/resource" +``` + +Proxy needs authentication? No problem: + +```ruby +HTTP.via("proxy-hostname.local", 8080, "username", "password") + .get "http://example.com/resource" +``` + + +### Adding Headers + +The HTTP gem uses the concept of chaining to simplify requests. Let's say +you want to get the latest commit of this library from Github in JSON format. +One way we could do this is by tacking a filename on the end of the URL: + +```ruby +HTTP.get "https://github.com/tarcieri/http/commit/HEAD.json" +``` + +The Github API happens to support this approach, but really this is a bit of a +hack that makes it easy for people typing URLs into the address bars of +browsers to perform the act of content negotiation. Since we have access to +the full, raw power of HTTP, we can perform content negotiation the way HTTP +intends us to, by using the Accept header: + +```ruby +HTTP.with_headers(:accept => 'application/json'). + get("https://github.com/tarcieri/http/commit/HEAD") +``` + +This requests JSON from Github. Github is smart enough to understand our +request and returns a response with Content-Type: application/json. If you +happen to have a library loaded which defines the JSON constant and implements +JSON.parse, the HTTP gem will attempt to parse the JSON response. + +Shorter aliases exists for HTTP.with_headers: + +```ruby +HTTP.with(:accept => 'application/json'). + get("https://github.com/tarcieri/http/commit/HEAD") + +HTTP[:accept => 'application/json']. + get("https://github.com/tarcieri/http/commit/HEAD") +``` + +### Content Negotiation + +As important a concept as content negotiation is to HTTP, it sure should be easy, +right? But usually it's not, and so we end up adding ".json" onto the ends of +our URLs because the existing mechanisms make it too hard. It should be easy: + +```ruby +HTTP.accept(:json).get("https://github.com/tarcieri/http/commit/HEAD") +``` + +This adds the appropriate Accept header for retrieving a JSON response for the +given resource. + + +### Celluloid::IO Support + +The HTTP Gem makes it simple to make multiple concurrent HTTP requests from a +Celluloid::IO actor. Here's a parallel HTTP fetcher with the HTTP Gem and +Celluloid::IO: + +```ruby +require 'celluloid/io' +require 'http' + +class HttpFetcher + include Celluloid::IO + + def fetch(url) + HTTP.get(url, socket_class: Celluloid::IO::TCPSocket) + end +end +``` + +There's a little more to it, but that's the core idea! + +* [Full parallel HTTP fetcher example](https://github.com/tarcieri/http/wiki/Parallel-requests-with-Celluloid%3A%3AIO) +* See also: [Celluloid::IO](https://github.com/celluloid/celluloid-io) + + +Supported Ruby Versions +----------------------- + +This library aims to support and is [tested against][travis] the following Ruby +versions: + +* Ruby 1.8.7 +* Ruby 1.9.2 +* Ruby 1.9.3 +* Ruby 2.0.0 +* Ruby 2.1.0 + +If something doesn't work on one of these versions, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby versions, +however support will only be provided for the versions listed above. + +If you would like this library to support another Ruby version or +implementation, you may volunteer to be a maintainer. Being a maintainer +entails making sure all tests run and pass on that implementation. When +something breaks on your implementation, you will be responsible for providing +patches in a timely fashion. If critical issues for a particular implementation +exist at the time of a major release, support for that Ruby version may be +dropped. + +[travis]: http://travis-ci.org/tarcieri/http + + +Contributing to The HTTP Gem +---------------------------- + +* Fork the HTTP gem on github +* Make your changes and send me a pull request +* If we like them we'll merge them +* If we've accepted a patch, feel free to ask for commit access! + +Copyright +--------- + +Copyright (c) 2014 Tony Arcieri, Erik Michaels-Ober. See LICENSE.txt for further details. diff --git a/.gems/gems/http-0.6.2/Rakefile b/.gems/gems/http-0.6.2/Rakefile new file mode 100644 index 0000000..b68f88e --- /dev/null +++ b/.gems/gems/http-0.6.2/Rakefile @@ -0,0 +1,29 @@ +#!/usr/bin/env rake +require 'bundler/gem_tasks' + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new + +task :test => :spec + +begin + require 'rubocop/rake_task' + RuboCop::RakeTask.new +rescue LoadError + task :rubocop do + $stderr.puts 'RuboCop is disabled' + end +end + +require 'yardstick/rake/measurement' +Yardstick::Rake::Measurement.new do |measurement| + measurement.output = 'measurement/report.txt' +end + +require 'yardstick/rake/verify' +Yardstick::Rake::Verify.new do |verify| + verify.require_exact_threshold = false + verify.threshold = 58 +end + +task :default => [:spec, :rubocop, :verify_measurements] diff --git a/.gems/gems/http-0.6.2/examples/parallel_requests_with_celluloid.rb b/.gems/gems/http-0.6.2/examples/parallel_requests_with_celluloid.rb new file mode 100755 index 0000000..f47e2ad --- /dev/null +++ b/.gems/gems/http-0.6.2/examples/parallel_requests_with_celluloid.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +# +# Example of using the HTTP Gem with Celluloid::IO +# Make sure to 'gem install celluloid-io' before running +# +# Run as: bundle exec examples/parallel_requests_with_celluloid.rb +# + +require 'celluloid/io' +require 'http' + +class HttpFetcher + include Celluloid::IO + + def fetch(url) + # Note: For SSL support specify: + # ssl_socket_class: Celluloid::IO::SSLSocket + HTTP.get(url, :socket_class => Celluloid::IO::TCPSocket) + end +end + +fetcher = HttpFetcher.new + +urls = %w[http://ruby-lang.org/ http://rubygems.org/ http://celluloid.io/] + +# Kick off a bunch of future calls to HttpFetcher to grab the URLs in parallel +futures = urls.map { |u| [u, fetcher.future.fetch(u)] } + +# Consume the results as they come in +futures.each do |url, future| + # Wait for HttpFetcher#fetch to complete for this request + response = future.value + puts "Got #{url}: #{response.inspect}" +end + +# Suppress Celluloid's shutdown messages +# Otherwise the example is a bit noisy :| +exit! diff --git a/.gems/gems/http-0.6.2/http.gemspec b/.gems/gems/http-0.6.2/http.gemspec new file mode 100644 index 0000000..b586e84 --- /dev/null +++ b/.gems/gems/http-0.6.2/http.gemspec @@ -0,0 +1,29 @@ +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'http/version' + +Gem::Specification.new do |gem| + gem.authors = %w[Tony Arcieri] + gem.email = %w[tony.arcieri@gmail.com] + + gem.description = <<-DESCRIPTION.strip.gsub(/\s+/, ' ') + An easy-to-use client library for making requests from Ruby. + It uses a simple method chaining system for building requests, + similar to Python's Requests. + DESCRIPTION + + gem.summary = 'HTTP should be easy' + gem.homepage = 'https://github.com/tarcieri/http' + gem.licenses = %w[MIT] + + gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = 'http' + gem.require_paths = %w[lib] + gem.version = HTTP::VERSION + + gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0' + + gem.add_development_dependency 'bundler', '~> 1.0' +end diff --git a/.gems/gems/http-0.6.2/lib/http.rb b/.gems/gems/http-0.6.2/lib/http.rb new file mode 100644 index 0000000..f4c8460 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http.rb @@ -0,0 +1,24 @@ +require 'http/parser' + +require 'http/errors' +require 'http/chainable' +require 'http/client' +require 'http/options' +require 'http/request' +require 'http/request/writer' +require 'http/response' +require 'http/response/body' +require 'http/response/parser' +require 'http/backports' + +# HTTP should be easy +module HTTP + extend Chainable + + class << self + # HTTP[:accept => 'text/html'].get(...) + alias_method :[], :with_headers + end +end + +Http = HTTP unless defined?(Http) diff --git a/.gems/gems/http-0.6.2/lib/http/authorization_header.rb b/.gems/gems/http-0.6.2/lib/http/authorization_header.rb new file mode 100644 index 0000000..d095b23 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/authorization_header.rb @@ -0,0 +1,37 @@ +module HTTP + # Authorization header value builders + module AuthorizationHeader + class << self + # Associate type with given builder. + # @param [#to_sym] type + # @param [Class] klass + # @return [void] + def register(type, klass) + builders[type.to_sym] = klass + end + + # Builds Authorization header value with associated builder. + # @param [#to_sym] type + # @param [Object] opts + # @return [String] + def build(type, opts) + klass = builders[type.to_sym] + + fail Error, "Unknown authorization type #{type}" unless klass + + klass.new opts + end + + private + + # :nodoc: + def builders + @builders ||= {} + end + end + end +end + +# built-in builders +require 'http/authorization_header/basic_auth' +require 'http/authorization_header/bearer_token' diff --git a/.gems/gems/http-0.6.2/lib/http/authorization_header/basic_auth.rb b/.gems/gems/http-0.6.2/lib/http/authorization_header/basic_auth.rb new file mode 100644 index 0000000..9f42eb7 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/authorization_header/basic_auth.rb @@ -0,0 +1,24 @@ +require 'base64' + +module HTTP + module AuthorizationHeader + # Basic authorization header builder + # @see http://tools.ietf.org/html/rfc2617 + class BasicAuth + # @param [#fetch] opts + # @option opts [#to_s] :user + # @option opts [#to_s] :pass + def initialize(opts) + @user = opts.fetch :user + @pass = opts.fetch :pass + end + + # :nodoc: + def to_s + 'Basic ' << Base64.strict_encode64("#{@user}:#{@pass}") + end + end + + register :basic, BasicAuth + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/authorization_header/bearer_token.rb b/.gems/gems/http-0.6.2/lib/http/authorization_header/bearer_token.rb new file mode 100644 index 0000000..ab5eed4 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/authorization_header/bearer_token.rb @@ -0,0 +1,28 @@ +require 'base64' + +module HTTP + module AuthorizationHeader + # OAuth2 Bearer token authorization header builder + # @see http://tools.ietf.org/html/rfc6750 + # + # @deprecated Will be remove in v0.7.0 + class BearerToken + # @param [#fetch] opts + # @option opts [#to_s] :token + # @option opts [Boolean] :encode (false) deprecated + def initialize(opts) + warn "#{Kernel.caller.first}: [DEPRECATION] BearerToken deprecated." + + @token = opts.fetch :token + @token = Base64.strict_encode64 @token if opts.fetch(:encode, false) + end + + # :nodoc: + def to_s + "Bearer #{@token}" + end + end + + register :bearer, BearerToken + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/backports.rb b/.gems/gems/http-0.6.2/lib/http/backports.rb new file mode 100644 index 0000000..2f61bb6 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/backports.rb @@ -0,0 +1,2 @@ +require 'http/backports/uri' if RUBY_VERSION < '1.9.3' +require 'http/backports/base64' if RUBY_VERSION < '1.9.0' diff --git a/.gems/gems/http-0.6.2/lib/http/backports/base64.rb b/.gems/gems/http-0.6.2/lib/http/backports/base64.rb new file mode 100644 index 0000000..5826bd2 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/backports/base64.rb @@ -0,0 +1,6 @@ +module Base64 + # :nodoc: + def self.strict_encode64(data) + encode64(data).gsub(/\n/, '') + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/backports/uri.rb b/.gems/gems/http-0.6.2/lib/http/backports/uri.rb new file mode 100644 index 0000000..9fca044 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/backports/uri.rb @@ -0,0 +1,131 @@ +# Taken from Ruby 1.9's uri/common.rb +# By Akira Yamada +# License: +# You can redistribute it and/or modify it under the same term as Ruby. + +require 'uri' + +# Backport Ruby 1.9's form encoding/decoding functionality +module URI + TBLENCWWWCOMP_ = {} # :nodoc: + 256.times do |i| + TBLENCWWWCOMP_[i.chr] = format('%%%02X', i) + end + TBLENCWWWCOMP_[' '] = '+' + TBLENCWWWCOMP_.freeze + TBLDECWWWCOMP_ = {} # :nodoc: + 256.times do |i| + h, l = i >> 4, i & 15 + TBLDECWWWCOMP_[format('%%%X%X', h, l)] = i.chr + TBLDECWWWCOMP_[format('%%%x%X', h, l)] = i.chr + TBLDECWWWCOMP_[format('%%%X%x', h, l)] = i.chr + TBLDECWWWCOMP_[format('%%%x%x', h, l)] = i.chr + end + TBLDECWWWCOMP_['+'] = ' ' + TBLDECWWWCOMP_.freeze + + # Encode given +str+ to URL-encoded form data. + # + # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP + # (ASCII space) to + and converts others to %XX. + # + # This is an implementation of + # http://www.w3.org/TR/html5/association-of-controls-and-forms.html#url-encoded-form-data + # + # See URI.decode_www_form_component, URI.encode_www_form + def self.encode_www_form_component(str) + str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/) { |chr| TBLENCWWWCOMP_[chr] } + end + + # Decode given +str+ of URL-encoded form data. + # + # This decods + to SP. + # + # See URI.encode_www_form_component, URI.decode_www_form + def self.decode_www_form_component(str) + fail(ArgumentError, "invalid %-encoding (#{str})") unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str + str.gsub(/\+|%\h\h/) { |chr| TBLDECWWWCOMP_[chr] } + end + + # Generate URL-encoded form data from given +enum+. + # + # This generates application/x-www-form-urlencoded data defined in HTML5 + # from given an Enumerable object. + # + # This internally uses URI.encode_www_form_component(str). + # + # This method doesn't convert the encoding of given items, so convert them + # before call this method if you want to send data as other than original + # encoding or mixed encoding data. (Strings which are encoded in an HTML5 + # ASCII incompatible encoding are converted to UTF-8.) + # + # This method doesn't handle files. When you send a file, use + # multipart/form-data. + # + # This is an implementation of + # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + # + # URI.encode_www_form([["q", "ruby"], ["lang", "en"]]) + # #=> "q=ruby&lang=en" + # URI.encode_www_form("q" => "ruby", "lang" => "en") + # #=> "q=ruby&lang=en" + # URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en") + # #=> "q=ruby&q=perl&lang=en" + # URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]]) + # #=> "q=ruby&q=perl&lang=en" + # + # See URI.encode_www_form_component, URI.decode_www_form + def self.encode_www_form(enum) + enum.map do |k, v| + if v.nil? + encode_www_form_component(k) + elsif v.respond_to?(:to_ary) + v.to_ary.map do |w| + next unless w + + str = encode_www_form_component(k) + str << '=' + str << encode_www_form_component(w) + end.join('&') + else + str = encode_www_form_component(k) + str << '=' + str << encode_www_form_component(v) + end + end.join('&') + end + + WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc: + + # Decode URL-encoded form data from given +str+. + # + # This decodes application/x-www-form-urlencoded data + # and returns array of key-value array. + # This internally uses URI.decode_www_form_component. + # + # _charset_ hack is not supported now because the mapping from given charset + # to Ruby's encoding is not clear yet. + # see also http://www.w3.org/TR/html5/syntax.html#character-encodings-0 + # + # This refers http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + # + # ary = URI.decode_www_form("a=1&a=2&b=3") + # p ary #=> [['a', '1'], ['a', '2'], ['b', '3']] + # p ary.assoc('a').last #=> '1' + # p ary.assoc('b').last #=> '3' + # p ary.rassoc('a').last #=> '2' + # p Hash[ary] # => {"a"=>"2", "b"=>"3"} + # + # See URI.decode_www_form_component, URI.encode_www_form + def self.decode_www_form(str) + return [] if str.empty? + unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str + fail(ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})") + end + ary = [] + $&.scan(/([^=;&]+)=([^;&]*)/) do + ary << [decode_www_form_component(Regexp.last_match[1]), decode_www_form_component(Regexp.last_match[2])] + end + ary + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/chainable.rb b/.gems/gems/http-0.6.2/lib/http/chainable.rb new file mode 100644 index 0000000..f223e67 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/chainable.rb @@ -0,0 +1,143 @@ +require 'http/authorization_header' + +module HTTP + module Chainable + # Request a get sans response body + def head(uri, options = {}) + request :head, uri, options + end + + # Get a resource + def get(uri, options = {}) + request :get, uri, options + end + + # Post to a resource + def post(uri, options = {}) + request :post, uri, options + end + + # Put to a resource + def put(uri, options = {}) + request :put, uri, options + end + + # Delete a resource + def delete(uri, options = {}) + request :delete, uri, options + end + + # Echo the request back to the client + def trace(uri, options = {}) + request :trace, uri, options + end + + # Return the methods supported on the given URI + def options(uri, options = {}) + request :options, uri, options + end + + # Convert to a transparent TCP/IP tunnel + def connect(uri, options = {}) + request :connect, uri, options + end + + # Apply partial modifications to a resource + def patch(uri, options = {}) + request :patch, uri, options + end + + # Make an HTTP request with the given verb + def request(verb, uri, options = {}) + branch(options).request verb, uri + end + + # Make a request through an HTTP proxy + def via(*proxy) + proxy_hash = {} + proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String) + proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a?(Integer) + proxy_hash[:proxy_username] = proxy[2] if proxy[2].is_a?(String) + proxy_hash[:proxy_password] = proxy[3] if proxy[3].is_a?(String) + + if [2, 4].include?(proxy_hash.keys.size) + branch default_options.with_proxy(proxy_hash) + else + fail(RequestError, "invalid HTTP proxy: #{proxy_hash}") + end + end + alias_method :through, :via + + # Alias for with_response(:object) + def stream + with_response(:object) + end + + # Make client follow redirects. + # @param opts (see Redirector#initialize) + # @return [HTTP::Client] + def follow(opts = true) + branch default_options.with_follow opts + end + + # (see #follow) + # @deprecated + alias_method :with_follow, :follow + + # Make a request with the given headers + def with_headers(headers) + branch default_options.with_headers(headers) + end + alias_method :with, :with_headers + + # Accept the given MIME type(s) + def accept(type) + with :accept => MimeType.normalize(type) + end + + # Make a request with the given Authorization header + def auth(*args) + value = case args.count + when 1 then args.first + when 2 then AuthorizationHeader.build(*args) + else fail ArgumentError, "wrong number of arguments (#{args.count} for 1..2)" + end + + with :authorization => value.to_s + end + + def default_options + @default_options ||= HTTP::Options.new + end + + def default_options=(opts) + @default_options = HTTP::Options.new(opts) + end + + def default_headers + default_options.headers + end + + def default_headers=(headers) + @default_options = default_options.dup do |opts| + opts.headers = headers + end + end + + def default_callbacks + default_options.callbacks + end + + def default_callbacks=(callbacks) + @default_options = default_options.dup do |opts| + opts.callbacks = callbacks + end + end + + private + + def branch(options) + HTTP::Client.new(options) + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/client.rb b/.gems/gems/http-0.6.2/lib/http/client.rb new file mode 100644 index 0000000..e6e03a5 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/client.rb @@ -0,0 +1,137 @@ +require 'cgi' +require 'uri' +require 'http/options' +require 'http/redirector' + +module HTTP + # Clients make requests and receive responses + class Client + include Chainable + + # Input buffer size + BUFFER_SIZE = 16_384 + + attr_reader :default_options + + def initialize(default_options = {}) + @default_options = HTTP::Options.new(default_options) + @parser = HTTP::Response::Parser.new + @socket = nil + end + + # Make an HTTP request + def request(verb, uri, opts = {}) + opts = @default_options.merge(opts) + uri = make_request_uri(uri, opts) + headers = opts.headers + proxy = opts.proxy + body = make_request_body(opts, headers) + + req = HTTP::Request.new(verb, uri, headers, proxy, body) + res = perform req, opts + + if opts.follow + res = Redirector.new(opts.follow).perform req, res do |request| + perform request, opts + end + end + + res + end + + # Perform a single (no follow) HTTP request + def perform(req, options) + # finish previous response if client was re-used + # TODO: this is pretty wrong, as socket shoud be part of response + # connection, so that re-use of client will not break multiple + # chunked responses + finish_response + + uri = req.uri + + # TODO: keep-alive support + @socket = options[:socket_class].open(req.socket_host, req.socket_port) + @socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS) + + req.stream @socket + + begin + read_more BUFFER_SIZE until @parser.headers + rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex + raise IOError, "problem making HTTP request: #{ex}" + end + + body = Response::Body.new(self) + res = Response.new(@parser.status_code, @parser.http_version, @parser.headers, body, uri) + + finish_response if :head == req.verb + + res + end + + # Read a chunk of the body + def readpartial(size = BUFFER_SIZE) + return unless @socket + + read_more size + chunk = @parser.chunk + + finish_response if @parser.finished? + + chunk.to_s + end + + private + + # Initialize TLS connection + def start_tls(socket, options) + # TODO: abstract away SSLContexts so we can use other TLS libraries + context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new + socket = options[:ssl_socket_class].new(socket, context) + + socket.connect + socket + end + + # Merges query params if needed + def make_request_uri(uri, options) + uri = URI uri.to_s unless uri.is_a? URI + + if options.params && !options.params.empty? + params = CGI.parse(uri.query.to_s).merge(options.params || {}) + uri.query = URI.encode_www_form params + end + + uri + end + + # Create the request body object to send + def make_request_body(opts, headers) + if opts.body + opts.body + elsif opts.form + headers['Content-Type'] ||= 'application/x-www-form-urlencoded' + URI.encode_www_form(opts.form) + elsif opts.json + headers['Content-Type'] ||= 'application/json' + MimeType[:json].encode opts.json + end + end + + # Callback for when we've reached the end of a response + def finish_response + @socket.close if @socket && !@socket.closed? + @parser.reset + + @socket = nil + end + + # Feeds some more data into parser + def read_more(size) + @parser << @socket.readpartial(size) unless @parser.finished? + true + rescue EOFError + false + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/content_type.rb b/.gems/gems/http-0.6.2/lib/http/content_type.rb new file mode 100644 index 0000000..1c45d32 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/content_type.rb @@ -0,0 +1,27 @@ +module HTTP + ContentType = Struct.new(:mime_type, :charset) do + MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)} + CHARSET_RE = /;\s*charset=([^;]+)/i + + class << self + # Parse string and return ContentType struct + def parse(str) + new mime_type(str), charset(str) + end + + private + + # :nodoc: + def mime_type(str) + md = str.to_s.match MIME_TYPE_RE + md && md[1].to_s.strip.downcase + end + + # :nodoc: + def charset(str) + md = str.to_s.match CHARSET_RE + md && md[1].to_s.strip.gsub(/^"|"$/, '') + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/errors.rb b/.gems/gems/http-0.6.2/lib/http/errors.rb new file mode 100644 index 0000000..18e0464 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/errors.rb @@ -0,0 +1,13 @@ +module HTTP + # Generic error + class Error < StandardError; end + + # Generic Request error + class RequestError < Error; end + + # Generic Response error + class ResponseError < Error; end + + # Request to do something when we're in the wrong state + class StateError < ResponseError; end +end diff --git a/.gems/gems/http-0.6.2/lib/http/headers.rb b/.gems/gems/http-0.6.2/lib/http/headers.rb new file mode 100644 index 0000000..4bba3b3 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/headers.rb @@ -0,0 +1,154 @@ +require 'forwardable' + +require 'http/headers/mixin' + +module HTTP + class Headers + extend Forwardable + include Enumerable + + # Matches HTTP header names when in "Canonical-Http-Format" + CANONICAL_HEADER = /^[A-Z][a-z]*(-[A-Z][a-z]*)*$/ + + # :nodoc: + def initialize + @pile = [] + end + + # Sets header + # + # @return [void] + def set(name, value) + delete(name) + add(name, value) + end + alias_method :[]=, :set + + # Removes header + # + # @return [void] + def delete(name) + name = canonicalize_header name.to_s + @pile.delete_if { |k, _| k == name } + end + + # Append header + # + # @return [void] + def add(name, value) + name = canonicalize_header name.to_s + Array(value).each { |v| @pile << [name, v] } + end + alias_method :append, :add + + # Return array of header values if any. + # + # @return [Array] + def get(name) + name = canonicalize_header name.to_s + @pile.select { |k, _| k == name }.map { |_, v| v } + end + + # Smart version of {#get} + # + # @return [NilClass] if header was not set + # @return [Object] if header has exactly one value + # @return [Array] if header has more than one value + def [](name) + values = get(name) + + case values.count + when 0 then nil + when 1 then values.first + else values + end + end + + # Converts headers into a Rack-compatible Hash + # + # @return [Hash] + def to_h + Hash[keys.map { |k| [k, self[k]] }] + end + + # Array of key/value pairs + # + # @return [Array<[String, String]>] + def to_a + @pile.map { |pair| pair.map(&:dup) } + end + + # :nodoc: + def inspect + "#<#{self.class} #{to_h.inspect}>" + end + + # List of header names + # + # @return [Array] + def keys + @pile.map { |k, _| k }.uniq + end + + # Compares headers to another Headers or Array of key/value pairs + # + # @return [Boolean] + def ==(other) + return false unless other.respond_to? :to_a + @pile == other.to_a + end + + def_delegators :@pile, :each, :empty?, :hash + + # :nodoc: + def initialize_copy(orig) + super + @pile = to_a + end + + # Merge in `other` headers + # + # @see #merge + # @return [void] + def merge!(other) + self.class.coerce(other).to_h.each { |name, values| set name, values } + end + + # Returns new Headers instance with `other` headers merged in. + # + # @see #merge! + # @return [Headers] + def merge(other) + dup.tap { |dupped| dupped.merge! other } + end + + # Initiates new Headers object from given object. + # + # @raise [Error] if given object can't be coerced + # @param [#to_hash, #to_h, #to_a] object + # @return [Headers] + def self.coerce(object) + unless object.is_a? self + object = case + when object.respond_to?(:to_hash) then object.to_hash + when object.respond_to?(:to_h) then object.to_h + when object.respond_to?(:to_a) then object.to_a + else fail Error, "Can't coerce #{object.inspect} to Headers" + end + end + + headers = new + object.each { |k, v| headers.add k, v } + headers + end + + private + + # Transform to canonical HTTP header capitalization + # @param [String] name + # @return [String] + def canonicalize_header(name) + name[CANONICAL_HEADER] || name.split(/[\-_]/).map(&:capitalize).join('-') + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/headers/mixin.rb b/.gems/gems/http-0.6.2/lib/http/headers/mixin.rb new file mode 100644 index 0000000..7ee0080 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/headers/mixin.rb @@ -0,0 +1,11 @@ +require 'forwardable' + +module HTTP + class Headers + module Mixin + extend Forwardable + attr_reader :headers + def_delegators :headers, :[], :[]= + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/mime_type.rb b/.gems/gems/http-0.6.2/lib/http/mime_type.rb new file mode 100644 index 0000000..826e222 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/mime_type.rb @@ -0,0 +1,76 @@ +module HTTP + # MIME type encode/decode adapters + module MimeType + class << self + # Associate MIME type with adapter + # + # @example + # + # module JsonAdapter + # class << self + # def encode(obj) + # # encode logic here + # end + # + # def decode(str) + # # decode logic here + # end + # end + # end + # + # HTTP::MimeType.register_adapter 'application/json', MyJsonAdapter + # + # @param [#to_s] type + # @param [#encode, #decode] adapter + # @return [void] + def register_adapter(type, adapter) + adapters[type.to_s] = adapter + end + + # Returns adapter associated with MIME type + # + # @param [#to_s] type + # @raise [Error] if no adapter found + # @return [Class] + def [](type) + adapters[normalize type] || fail(Error, "Unknown MIME type: #{type}") + end + + # Register a shortcut for MIME type + # + # @example + # + # HTTP::MimeType.register_alias 'application/json', :json + # + # @param [#to_s] type + # @param [#to_sym] shortcut + # @return [void] + def register_alias(type, shortcut) + aliases[shortcut.to_sym] = type.to_s + end + + # Resolves type by shortcut if possible + # + # @param [#to_s] type + # @return [String] + def normalize(type) + aliases.fetch type, type.to_s + end + + private + + # :nodoc: + def adapters + @adapters ||= {} + end + + # :nodoc: + def aliases + @aliases ||= {} + end + end + end +end + +# built-in mime types +require 'http/mime_type/json' diff --git a/.gems/gems/http-0.6.2/lib/http/mime_type/adapter.rb b/.gems/gems/http-0.6.2/lib/http/mime_type/adapter.rb new file mode 100644 index 0000000..ac45b48 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/mime_type/adapter.rb @@ -0,0 +1,24 @@ +require 'forwardable' +require 'singleton' + +module HTTP + module MimeType + # Base encode/decode MIME type adapter + class Adapter + include Singleton + + class << self + extend Forwardable + def_delegators :instance, :encode, :decode + end + + %w[encode decode].each do |operation| + class_eval <<-RUBY, __FILE__, __LINE__ + def #{operation}(*) + fail Error, "\#{self.class} does not supports ##{operation}" + end + RUBY + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/mime_type/json.rb b/.gems/gems/http-0.6.2/lib/http/mime_type/json.rb new file mode 100644 index 0000000..b2604e7 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/mime_type/json.rb @@ -0,0 +1,23 @@ +require 'json' +require 'http/mime_type/adapter' + +module HTTP + module MimeType + # JSON encode/decode MIME type adapter + class JSON < Adapter + # Encodes object to JSON + def encode(obj) + return obj.to_json if obj.respond_to?(:to_json) + ::JSON.dump obj + end + + # Decodes JSON + def decode(str) + ::JSON.load str + end + end + + register_adapter 'application/json', JSON + register_alias 'application/json', :json + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/options.rb b/.gems/gems/http-0.6.2/lib/http/options.rb new file mode 100644 index 0000000..ca8f955 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/options.rb @@ -0,0 +1,130 @@ +require 'http/headers' +require 'openssl' +require 'socket' + +module HTTP + class Options + # How to format the response [:object, :body, :parse_body] + attr_accessor :response + + # HTTP headers to include in the request + attr_accessor :headers + + # Query string params to add to the url + attr_accessor :params + + # Form data to embed in the request + attr_accessor :form + + # JSON data to embed in the request + attr_accessor :json + + # Explicit request body of the request + attr_accessor :body + + # HTTP proxy to route request + attr_accessor :proxy + + # Socket classes + attr_accessor :socket_class, :ssl_socket_class + + # SSL context + attr_accessor :ssl_context + + # Follow redirects + attr_accessor :follow + + protected :response=, :headers=, :proxy=, :params=, :form=, :json=, :follow= + + @default_socket_class = TCPSocket + @default_ssl_socket_class = OpenSSL::SSL::SSLSocket + + class << self + attr_accessor :default_socket_class, :default_ssl_socket_class + + def new(options = {}) + return options if options.is_a?(self) + super + end + end + + def initialize(options = {}) + @response = options[:response] || :auto + @proxy = options[:proxy] || {} + @body = options[:body] + @params = options[:params] + @form = options[:form] + @json = options[:json] + @follow = options[:follow] + + @headers = HTTP::Headers.coerce(options[:headers] || {}) + + @socket_class = options[:socket_class] || self.class.default_socket_class + @ssl_socket_class = options[:ssl_socket_class] || self.class.default_ssl_socket_class + @ssl_context = options[:ssl_context] + end + + def with_headers(headers) + dup do |opts| + opts.headers = self.headers.merge(headers) + end + end + + %w[proxy params form json body follow].each do |method_name| + class_eval <<-RUBY, __FILE__, __LINE__ + def with_#{method_name}(value) + dup { |opts| opts.#{method_name} = value } + end + RUBY + end + + def [](option) + send(option) rescue nil + end + + def merge(other) + h1, h2 = to_hash, other.to_hash + merged = h1.merge(h2) do |k, v1, v2| + case k + when :headers + v1.merge(v2) + else + v2 + end + end + + self.class.new(merged) + end + + def to_hash + # FIXME: hardcoding these fields blows! We should have a declarative + # way of specifying all the options fields, and ensure they *all* + # get serialized here, rather than manually having to add them each time + { + :response => response, + :headers => headers.to_h, + :proxy => proxy, + :params => params, + :form => form, + :json => json, + :body => body, + :follow => follow, + :socket_class => socket_class, + :ssl_socket_class => ssl_socket_class, + :ssl_context => ssl_context + } + end + + def dup + dupped = super + yield(dupped) if block_given? + dupped + end + + private + + def argument_error!(message) + fail(Error, message, caller[1..-1]) + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/redirector.rb b/.gems/gems/http-0.6.2/lib/http/redirector.rb new file mode 100644 index 0000000..7cb8f5e --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/redirector.rb @@ -0,0 +1,66 @@ +module HTTP + class Redirector + # Notifies that we reached max allowed redirect hops + class TooManyRedirectsError < ResponseError; end + + # Notifies that following redirects got into an endless loop + class EndlessRedirectError < TooManyRedirectsError; end + + # HTTP status codes which indicate redirects + REDIRECT_CODES = [300, 301, 302, 303, 307, 308].freeze + + # :nodoc: + def initialize(options = nil) + options = {:max_hops => 5} unless options.respond_to?(:fetch) + @max_hops = options.fetch(:max_hops, 5) + @max_hops = false if @max_hops && 1 > @max_hops.to_i + end + + # Follows redirects until non-redirect response found + def perform(request, response, &block) + reset(request, response) + follow(&block) + end + + private + + # Reset redirector state + def reset(request, response) + @request, @response = request, response + @visited = [] + end + + # Follow redirects + def follow + while REDIRECT_CODES.include?(@response.code) + @visited << @request.uri.to_s + + fail TooManyRedirectsError if too_many_hops? + fail EndlessRedirectError if endless_loop? + + uri = @response.headers['Location'] + fail StateError, 'no Location header in redirect' unless uri + + if 303 == @response.code + @request = @request.redirect uri, :get + else + @request = @request.redirect uri + end + + @response = yield @request + end + + @response + end + + # Check if we reached max amount of redirect hops + def too_many_hops? + @max_hops < @visited.count if @max_hops + end + + # Check if we got into an endless loop + def endless_loop? + 2 < @visited.count(@visited.last) + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/request.rb b/.gems/gems/http-0.6.2/lib/http/request.rb new file mode 100644 index 0000000..5fca9e1 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/request.rb @@ -0,0 +1,153 @@ +require 'http/errors' +require 'http/headers' +require 'http/request/writer' +require 'http/version' +require 'base64' +require 'uri' + +module HTTP + class Request + include HTTP::Headers::Mixin + + # The method given was not understood + class UnsupportedMethodError < RequestError; end + + # The scheme of given URI was not understood + class UnsupportedSchemeError < RequestError; end + + # Default User-Agent header value + USER_AGENT = "RubyHTTPGem/#{HTTP::VERSION}".freeze + + # RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 + METHODS = [:options, :get, :head, :post, :put, :delete, :trace, :connect] + + # RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV + METHODS.concat [:propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock] + + # RFC 3648: WebDAV Ordered Collections Protocol + METHODS.concat [:orderpatch] + + # RFC 3744: WebDAV Access Control Protocol + METHODS.concat [:acl] + + # draft-dusseault-http-patch: PATCH Method for HTTP + METHODS.concat [:patch] + + # draft-reschke-webdav-search: WebDAV Search + METHODS.concat [:search] + + # Allowed schemes + SCHEMES = [:http, :https, :ws, :wss] + + # Default ports of supported schemes + PORTS = { + :http => 80, + :https => 443, + :ws => 80, + :wss => 443 + } + + # Method is given as a lowercase symbol e.g. :get, :post + attr_reader :verb + + # Scheme is normalized to be a lowercase symbol e.g. :http, :https + attr_reader :scheme + + # The following alias may be removed in three minor versions (0.8.0) or one + # major version (1.0.0) + alias_method :__method__, :method + + # The following method may be removed in two minor versions (0.7.0) or one + # major version (1.0.0) + def method(*) + warn "#{Kernel.caller.first}: [DEPRECATION] HTTP::Request#method is deprecated. Use #verb instead. For Object#method, use #__method__." + @verb + end + + # "Request URI" as per RFC 2616 + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html + attr_reader :uri + attr_reader :proxy, :body, :version + + # :nodoc: + def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = '1.1') # rubocop:disable ParameterLists + @verb = verb.to_s.downcase.to_sym + @uri = uri.is_a?(URI) ? uri : URI(uri.to_s) + @scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme + + fail(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb) + fail(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme) + + @proxy, @body, @version = proxy, body, version + + @headers = HTTP::Headers.coerce(headers || {}) + + @headers['Host'] ||= default_host + @headers['User-Agent'] ||= USER_AGENT + end + + # Returns new Request with updated uri + def redirect(uri, verb = @verb) + uri = @uri.merge uri.to_s + req = self.class.new(verb, uri, headers, proxy, body, version) + req['Host'] = req.uri.host + req + end + + # Stream the request to a socket + def stream(socket) + include_proxy_authorization_header if using_authenticated_proxy? + Request::Writer.new(socket, body, headers, request_header).stream + end + + # Is this request using a proxy? + def using_proxy? + proxy && proxy.keys.size >= 2 + end + + # Is this request using an authenticated proxy? + def using_authenticated_proxy? + proxy && proxy.keys.size == 4 + end + + # Compute and add the Proxy-Authorization header + def include_proxy_authorization_header + digest = Base64.encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}").chomp + headers['Proxy-Authorization'] = "Basic #{digest}" + end + + # Compute HTTP request header for direct or proxy request + def request_header + if using_proxy? + "#{verb.to_s.upcase} #{uri} HTTP/#{version}" + else + path = uri.query && !uri.query.empty? ? "#{uri.path}?#{uri.query}" : uri.path + path = '/' if path.empty? + "#{verb.to_s.upcase} #{path} HTTP/#{version}" + end + end + + # Host for tcp socket + def socket_host + using_proxy? ? proxy[:proxy_address] : uri.host + end + + # Port for tcp socket + def socket_port + using_proxy? ? proxy[:proxy_port] : uri.port + end + + private + + # Default host (with port if needed) header value. + # + # @return [String] + def default_host + if PORTS[@scheme] == @uri.port + @uri.host + else + "#{@uri.host}:#{@uri.port}" + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/request/writer.rb b/.gems/gems/http-0.6.2/lib/http/request/writer.rb new file mode 100644 index 0000000..f4044ce --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/request/writer.rb @@ -0,0 +1,84 @@ +module HTTP + class Request + class Writer + # CRLF is the universal HTTP delimiter + CRLF = "\r\n" + + # Types valid to be used as body source + VALID_BODY_TYPES = [String, NilClass, Enumerable] + + def initialize(socket, body, headers, headerstart) # rubocop:disable ParameterLists + @body = body + @socket = socket + @headers = headers + @request_header = [headerstart] + + validate_body_type! + end + + # Adds headers to the request header from the headers array + def add_headers + @headers.each do |field, value| + @request_header << "#{field}: #{value}" + end + end + + # Stream the request to a socket + def stream + send_request_header + send_request_body + end + + # Adds the headers to the header array for the given request body we are working + # with + def add_body_type_headers + if @body.is_a?(String) && !@headers['Content-Length'] + @request_header << "Content-Length: #{@body.bytesize}" + elsif @body.is_a?(Enumerable) + encoding = @headers['Transfer-Encoding'] + if encoding == 'chunked' + @request_header << 'Transfer-Encoding: chunked' + else + fail(RequestError, 'invalid transfer encoding') + end + end + end + + # Joins the headers specified in the request into a correctly formatted + # http request header string + def join_headers + # join the headers array with crlfs, stick two on the end because + # that ends the request header + @request_header.join(CRLF) + (CRLF) * 2 + end + + def send_request_header + add_headers + add_body_type_headers + header = join_headers + + @socket << header + end + + def send_request_body + if @body.is_a?(String) + @socket << @body + elsif @body.is_a?(Enumerable) + @body.each do |chunk| + @socket << chunk.bytesize.to_s(16) << CRLF + @socket << chunk << CRLF + end + + @socket << '0' << CRLF * 2 + end + end + + private + + def validate_body_type! + return if VALID_BODY_TYPES.any? { |type| @body.is_a? type } + fail RequestError, "body of wrong type: #{@body.class}" + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/response.rb b/.gems/gems/http-0.6.2/lib/http/response.rb new file mode 100644 index 0000000..41ee941 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/response.rb @@ -0,0 +1,137 @@ +require 'delegate' +require 'http/headers' +require 'http/content_type' +require 'http/mime_type' + +module HTTP + class Response + include HTTP::Headers::Mixin + + STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => "I'm a Teapot", + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 426 => 'Upgrade Required', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 510 => 'Not Extended' + } + STATUS_CODES.freeze + + SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |code, msg| [msg.downcase.gsub(/\s|-/, '_').to_sym, code] }] + SYMBOL_TO_STATUS_CODE.freeze + + attr_reader :status + attr_reader :body + attr_reader :uri + + # Status aliases! TIMTOWTDI!!! (Want to be idiomatic? Just use status :) + alias_method :code, :status + alias_method :status_code, :status + + def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists + @status, @version, @body, @uri = status, version, body, uri + @headers = HTTP::Headers.coerce(headers || {}) + end + + # Obtain the 'Reason-Phrase' for the response + def reason + STATUS_CODES[@status] + end + + # Returns an Array ala Rack: `[status, headers, body]` + def to_a + [status, headers.to_h, body.to_s] + end + + # Return the response body as a string + def to_s + body.to_s + end + alias_method :to_str, :to_s + + # Flushes body and returns self-reference + def flush + body.to_s + self + end + + # Parsed Content-Type header + # @return [HTTP::ContentType] + def content_type + @content_type ||= ContentType.parse headers['Content-Type'] + end + + # MIME type of response (if any) + # @return [String, nil] + def mime_type + @mime_type ||= content_type.mime_type + end + + # Charset of response (if any) + # @return [String, nil] + def charset + @charset ||= content_type.charset + end + + # Parse response body with corresponding MIME type adapter. + # + # @param [#to_s] as Parse as given MIME type + # instead of the one determined from headers + # @raise [Error] if adapter not found + # @return [Object] + def parse(as = nil) + MimeType[as || mime_type].decode to_s + end + + # Inspect a response + def inspect + "#<#{self.class}/#{@version} #{status} #{reason} headers=#{headers.inspect}>" + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/response/body.rb b/.gems/gems/http-0.6.2/lib/http/response/body.rb new file mode 100644 index 0000000..7bb30c9 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/response/body.rb @@ -0,0 +1,64 @@ +require 'forwardable' +require 'http/client' + +module HTTP + class Response + # A streamable response body, also easily converted into a string + class Body + extend Forwardable + include Enumerable + def_delegator :to_s, :empty? + + def initialize(client) + @client = client + @streaming = nil + @contents = nil + end + + # Read up to length bytes, but return any data that's available + # @see HTTP::Client#readpartial + def readpartial(*args) + stream! + @client.readpartial(*args) + end + + # Iterate over the body, allowing it to be enumerable + def each + while (chunk = readpartial) + yield chunk + end + end + + # Eagerly consume the entire body as a string + def to_s + return @contents if @contents + fail StateError, 'body is being streamed' unless @streaming.nil? + + begin + @streaming = false + @contents = '' + while (chunk = @client.readpartial) + @contents << chunk + end + rescue + @contents = nil + raise + end + + @contents + end + alias_method :to_str, :to_s + + # Assert that the body is actively being streamed + def stream! + fail StateError, 'body has already been consumed' if @streaming == false + @streaming = true + end + + # Easier to interpret string inspect + def inspect + "#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>" + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/response/parser.rb b/.gems/gems/http-0.6.2/lib/http/response/parser.rb new file mode 100644 index 0000000..67c3ec1 --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/response/parser.rb @@ -0,0 +1,66 @@ +module HTTP + class Response + class Parser + attr_reader :headers + + def initialize + @parser = HTTP::Parser.new(self) + reset + end + + def add(data) + @parser << data + end + alias_method :<<, :add + + def headers? + !!@headers + end + + def http_version + @parser.http_version.join('.') + end + + def status_code + @parser.status_code + end + + # + # HTTP::Parser callbacks + # + + def on_headers_complete(headers) + @headers = headers + end + + def on_body(chunk) + if @chunk + @chunk << chunk + else + @chunk = chunk + end + end + + def chunk + chunk, @chunk = @chunk, nil + chunk + end + + def on_message_complete + @finished = true + end + + def reset + @parser.reset! + + @finished = false + @headers = nil + @chunk = nil + end + + def finished? + @finished + end + end + end +end diff --git a/.gems/gems/http-0.6.2/lib/http/version.rb b/.gems/gems/http-0.6.2/lib/http/version.rb new file mode 100644 index 0000000..883c90a --- /dev/null +++ b/.gems/gems/http-0.6.2/lib/http/version.rb @@ -0,0 +1,3 @@ +module HTTP + VERSION = '0.6.2' +end diff --git a/.gems/gems/http-0.6.2/logo.png b/.gems/gems/http-0.6.2/logo.png new file mode 100644 index 0000000..8316879 Binary files /dev/null and b/.gems/gems/http-0.6.2/logo.png differ diff --git a/.gems/gems/http-0.6.2/spec/http/authorization_header/basic_auth_spec.rb b/.gems/gems/http-0.6.2/spec/http/authorization_header/basic_auth_spec.rb new file mode 100644 index 0000000..6ddbd5b --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/authorization_header/basic_auth_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe HTTP::AuthorizationHeader::BasicAuth do + describe '.new' do + it 'fails when options is not a Hash' do + expect { described_class.new '[FOOBAR]' }.to raise_error + end + + it 'fails when :pass is not given' do + expect { described_class.new :user => '[USER]' }.to raise_error + end + + it 'fails when :user is not given' do + expect { described_class.new :pass => '[PASS]' }.to raise_error + end + end + + describe '#to_s' do + let(:user) { 'foo' } + let(:pass) { 'bar' * 100 } + let(:user_n_pass) { user + ':' + pass } + let(:builder) { described_class.new :user => user, :pass => pass } + + subject { builder.to_s } + + it { should eq "Basic #{Base64.strict_encode64 user_n_pass}" } + it { should match(/^Basic [^\s]+$/) } + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/authorization_header/bearer_token_spec.rb b/.gems/gems/http-0.6.2/spec/http/authorization_header/bearer_token_spec.rb new file mode 100644 index 0000000..02bf75f --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/authorization_header/bearer_token_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe HTTP::AuthorizationHeader::BearerToken do + describe '.new' do + it 'fails when options is not a Hash' do + expect { described_class.new '[TOKEN]' }.to raise_error + end + + it 'fails when :token is not given' do + expect { described_class.new :encode => true }.to raise_error + end + end + + describe '#to_s' do + let(:token) { 'foobar' * 100 } + let(:builder) { described_class.new options.merge :token => token } + + subject { builder.to_s } + + context 'when :encode => true' do + let(:options) { {:encode => true} } + it { should eq "Bearer #{Base64.strict_encode64 token}" } + it { should match(/^Bearer [^\s]+$/) } + end + + context 'when :encode => false' do + let(:options) { {:encode => false} } + it { should eq "Bearer #{token}" } + end + + context 'when :encode not specified' do + let(:options) { {} } + it { should eq "Bearer #{token}" } + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/authorization_header_spec.rb b/.gems/gems/http-0.6.2/spec/http/authorization_header_spec.rb new file mode 100644 index 0000000..b91bf44 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/authorization_header_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe HTTP::AuthorizationHeader do + describe '.build' do + context 'with unkown type' do + let(:type) { :foobar } + let(:opts) { {:foo => :bar} } + + it 'fails' do + expect { described_class.build type, opts }.to raise_error + end + end + + context 'with :basic type' do + let(:type) { :basic } + let(:opts) { {:user => 'user', :pass => 'pass'} } + + it 'passes options to BasicAuth' do + expect(described_class::BasicAuth).to receive(:new).with(opts) + described_class.build type, opts + end + end + + context 'with :bearer type' do + let(:type) { :bearer } + let(:opts) { {:token => 'token', :encode => true} } + + it 'passes options to BearerToken' do + expect(described_class::BearerToken).to receive(:new).with(opts) + described_class.build type, opts + end + end + end + + describe '.register' do + it 'registers given klass in builders registry' do + described_class.register :dummy, Class.new { def initialize(*); end } + expect { described_class.build(:dummy, 'foobar') }.to_not raise_error + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/backports/base64_spec.rb b/.gems/gems/http-0.6.2/spec/http/backports/base64_spec.rb new file mode 100644 index 0000000..77d4968 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/backports/base64_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Base64 do + specify { expect(Base64).to respond_to :strict_encode64 } + + describe '.strict_encode64' do + let(:long_string) { (0...256).map { ('a'..'z').to_a[rand(26)] }.join } + + it 'returns a String without whitespaces' do + expect(Base64.strict_encode64 long_string).to_not match(/\s/) + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/backports/uri_spec.rb b/.gems/gems/http-0.6.2/spec/http/backports/uri_spec.rb new file mode 100644 index 0000000..a295cf2 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/backports/uri_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe URI do + describe '.encode_www_form' do + it 'properly encodes arrays' do + expect(URI.encode_www_form :a => [:b, :c]).to eq 'a=b&a=c' + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/client_spec.rb b/.gems/gems/http-0.6.2/spec/http/client_spec.rb new file mode 100644 index 0000000..814e833 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/client_spec.rb @@ -0,0 +1,202 @@ +require 'spec_helper' + +describe HTTP::Client do + StubbedClient = Class.new(HTTP::Client) do + def perform(request, options) + stubs.fetch(request.uri.to_s) { super(request, options) } + end + + def stubs + @stubs ||= {} + end + + def stub(stubs) + @stubs = stubs + self + end + end + + def redirect_response(location, status = 302) + HTTP::Response.new(status, '1.1', {'Location' => location}, '') + end + + def simple_response(body, status = 200) + HTTP::Response.new(status, '1.1', {}, body) + end + + describe 'following redirects' do + it 'returns response of new location' do + client = StubbedClient.new(:follow => true).stub( + 'http://example.com/' => redirect_response('http://example.com/blog'), + 'http://example.com/blog' => simple_response('OK') + ) + + expect(client.get('http://example.com/').to_s).to eq 'OK' + end + + it 'prepends previous request uri scheme and host if needed' do + client = StubbedClient.new(:follow => true).stub( + 'http://example.com/' => redirect_response('/index'), + 'http://example.com/index' => redirect_response('/index.html'), + 'http://example.com/index.html' => simple_response('OK') + ) + + expect(client.get('http://example.com/').to_s).to eq 'OK' + end + + it 'fails upon endless redirects' do + client = StubbedClient.new(:follow => true).stub( + 'http://example.com/' => redirect_response('/') + ) + + expect { client.get('http://example.com/') } \ + .to raise_error(HTTP::Redirector::EndlessRedirectError) + end + + it 'fails if max amount of hops reached' do + client = StubbedClient.new(:follow => 5).stub( + 'http://example.com/' => redirect_response('/1'), + 'http://example.com/1' => redirect_response('/2'), + 'http://example.com/2' => redirect_response('/3'), + 'http://example.com/3' => redirect_response('/4'), + 'http://example.com/4' => redirect_response('/5'), + 'http://example.com/5' => redirect_response('/6'), + 'http://example.com/6' => simple_response('OK') + ) + + expect { client.get('http://example.com/') } \ + .to raise_error(HTTP::Redirector::TooManyRedirectsError) + end + end + + describe 'parsing params' do + let(:client) { HTTP::Client.new } + before { allow(client).to receive :perform } + + it 'accepts params within the provided URL' do + expect(HTTP::Request).to receive(:new) do |_, uri| + expect(CGI.parse uri.query).to eq('foo' => %w[bar]) + end + + client.get('http://example.com/?foo=bar') + end + + it 'combines GET params from the URI with the passed in params' do + expect(HTTP::Request).to receive(:new) do |_, uri| + expect(CGI.parse uri.query).to eq('foo' => %w[bar], 'baz' => %w[quux]) + end + + client.get('http://example.com/?foo=bar', :params => {:baz => 'quux'}) + end + + it 'merges duplicate values' do + expect(HTTP::Request).to receive(:new) do |_, uri| + expect(uri.query).to match(/^(a=1&a=2|a=2&a=1)$/) + end + + client.get('http://example.com/?a=1', :params => {:a => 2}) + end + + it 'does not modifies query part if no params were given' do + expect(HTTP::Request).to receive(:new) do |_, uri| + expect(uri.query).to eq 'deadbeef' + end + + client.get('http://example.com/?deadbeef') + end + + it 'does not corrupts index-less arrays' do + expect(HTTP::Request).to receive(:new) do |_, uri| + expect(CGI.parse uri.query).to eq 'a[]' => %w[b c], 'd' => %w[e] + end + + client.get('http://example.com/?a[]=b&a[]=c', :params => {:d => 'e'}) + end + end + + describe 'passing json' do + it 'encodes given object' do + client = HTTP::Client.new + allow(client).to receive(:perform) + + expect(HTTP::Request).to receive(:new) do |*args| + expect(args.last).to eq('{"foo":"bar"}') + end + + client.get('http://example.com/', :json => {:foo => :bar}) + end + end + + describe '#request' do + context 'with explicitly given `Host` header' do + let(:headers) { {'Host' => 'another.example.com'} } + let(:client) { described_class.new :headers => headers } + + it 'keeps `Host` header as is' do + expect(client).to receive(:perform) do |req, _| + expect(req['Host']).to eq 'another.example.com' + end + + client.request(:get, 'http://example.com/') + end + end + end + + describe '#perform' do + let(:client) { described_class.new } + + it 'calls finish_response before actual performance' do + TCPSocket.stub(:open) { throw :halt } + expect(client).to receive(:finish_response) + catch(:halt) { client.head "http://127.0.0.1:#{ExampleService::PORT}/" } + end + + it 'calls finish_response once body was fully flushed' do + expect(client).to receive(:finish_response).twice.and_call_original + client.get("http://127.0.0.1:#{ExampleService::PORT}/").to_s + end + + context 'with HEAD request' do + it 'does not iterates through body' do + expect(client).to_not receive(:readpartial) + client.head("http://127.0.0.1:#{ExampleService::PORT}/") + end + + it 'finishes response after headers were received' do + expect(client).to receive(:finish_response).twice.and_call_original + client.head("http://127.0.0.1:#{ExampleService::PORT}/") + end + end + + context 'when server fully flushes response in one chunk' do + before do + socket_spy = double + + chunks = [ + <<-RESPONSE.gsub(/^\s*\| */, '').gsub(/\n/, "\r\n") + | HTTP/1.1 200 OK + | Content-Type: text/html + | Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) + | Date: Mon, 24 Mar 2014 00:32:22 GMT + | Content-Length: 15 + | Connection: Keep-Alive + | + | + RESPONSE + ] + + socket_spy.stub(:close) { nil } + socket_spy.stub(:closed?) { true } + socket_spy.stub(:readpartial) { chunks.shift } + socket_spy.stub(:<<) { nil } + + TCPSocket.stub(:open) { socket_spy } + end + + it 'properly reads body' do + body = client.get("http://127.0.0.1:#{ExampleService::PORT}/").to_s + expect(body).to eq '' + end + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/content_type_spec.rb b/.gems/gems/http-0.6.2/spec/http/content_type_spec.rb new file mode 100644 index 0000000..b27379f --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/content_type_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe HTTP::ContentType do + describe '.parse' do + context 'with text/plain' do + subject { described_class.parse 'text/plain' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should be_nil } + end + + context 'with tEXT/plaIN' do + subject { described_class.parse 'tEXT/plaIN' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should be_nil } + end + + context 'with text/plain; charset=utf-8' do + subject { described_class.parse 'text/plain; charset=utf-8' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should eq 'utf-8' } + end + + context 'with text/plain; charset="utf-8"' do + subject { described_class.parse 'text/plain; charset="utf-8"' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should eq 'utf-8' } + end + + context 'with text/plain; charSET=utf-8' do + subject { described_class.parse 'text/plain; charSET=utf-8' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should eq 'utf-8' } + end + + context 'with text/plain; foo=bar; charset=utf-8' do + subject { described_class.parse 'text/plain; foo=bar; charset=utf-8' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should eq 'utf-8' } + end + + context 'with text/plain;charset=utf-8;foo=bar' do + subject { described_class.parse 'text/plain;charset=utf-8;foo=bar' } + its(:mime_type) { should eq 'text/plain' } + its(:charset) { should eq 'utf-8' } + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/headers/mixin_spec.rb b/.gems/gems/http-0.6.2/spec/http/headers/mixin_spec.rb new file mode 100644 index 0000000..bd72c6d --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/headers/mixin_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe HTTP::Headers::Mixin do + let :dummy_class do + Class.new do + include HTTP::Headers::Mixin + + def initialize(headers) + @headers = headers + end + end + end + + let(:headers) { HTTP::Headers.new } + let(:dummy) { dummy_class.new headers } + + describe '#headers' do + it 'returns @headers instance variable' do + expect(dummy.headers).to be headers + end + end + + describe '#[]' do + it 'proxies to headers#[]' do + expect(headers).to receive(:[]).with(:accept) + dummy[:accept] + end + end + + describe '#[]=' do + it 'proxies to headers#[]' do + expect(headers).to receive(:[]=).with(:accept, 'text/plain') + dummy[:accept] = 'text/plain' + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/headers_spec.rb b/.gems/gems/http-0.6.2/spec/http/headers_spec.rb new file mode 100644 index 0000000..c362376 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/headers_spec.rb @@ -0,0 +1,417 @@ +require 'spec_helper' + +describe HTTP::Headers do + subject(:headers) { described_class.new } + + it 'is Enumerable' do + expect(headers).to be_an Enumerable + end + + describe '#set' do + it 'sets header value' do + headers.set 'Accept', 'application/json' + expect(headers['Accept']).to eq 'application/json' + end + + it 'normalizes header name' do + headers.set :content_type, 'application/json' + expect(headers['Content-Type']).to eq 'application/json' + end + + it 'overwrites previous value' do + headers.set :set_cookie, 'hoo=ray' + headers.set :set_cookie, 'woo=hoo' + expect(headers['Set-Cookie']).to eq 'woo=hoo' + end + + it 'allows set multiple values' do + headers.set :set_cookie, 'hoo=ray' + headers.set :set_cookie, %w[hoo=ray woo=hoo] + expect(headers['Set-Cookie']).to eq %w[hoo=ray woo=hoo] + end + end + + describe '#[]=' do + it 'sets header value' do + headers['Accept'] = 'application/json' + expect(headers['Accept']).to eq 'application/json' + end + + it 'normalizes header name' do + headers[:content_type] = 'application/json' + expect(headers['Content-Type']).to eq 'application/json' + end + + it 'overwrites previous value' do + headers[:set_cookie] = 'hoo=ray' + headers[:set_cookie] = 'woo=hoo' + expect(headers['Set-Cookie']).to eq 'woo=hoo' + end + + it 'allows set multiple values' do + headers[:set_cookie] = 'hoo=ray' + headers[:set_cookie] = %w[hoo=ray woo=hoo] + expect(headers['Set-Cookie']).to eq %w[hoo=ray woo=hoo] + end + end + + describe '#delete' do + before { headers.set 'Content-Type', 'application/json' } + + it 'removes given header' do + headers.delete 'Content-Type' + expect(headers['Content-Type']).to be_nil + end + + it 'normalizes header name' do + headers.delete :content_type + expect(headers['Content-Type']).to be_nil + end + end + + describe '#add' do + it 'sets header value' do + headers.add 'Accept', 'application/json' + expect(headers['Accept']).to eq 'application/json' + end + + it 'normalizes header name' do + headers.add :content_type, 'application/json' + expect(headers['Content-Type']).to eq 'application/json' + end + + it 'appends new value if header exists' do + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, 'woo=hoo' + expect(headers['Set-Cookie']).to eq %w[hoo=ray woo=hoo] + end + + it 'allows append multiple values' do + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, %w[woo=hoo yup=pie] + expect(headers['Set-Cookie']).to eq %w[hoo=ray woo=hoo yup=pie] + end + end + + describe '#get' do + before { headers.set 'Content-Type', 'application/json' } + + it 'returns array of associated values' do + expect(headers.get 'Content-Type').to eq %w[application/json] + end + + it 'normalizes header name' do + expect(headers.get :content_type).to eq %w[application/json] + end + + context 'when header does not exists' do + it 'returns empty array' do + expect(headers.get :accept).to eq [] + end + end + end + + describe '#[]' do + context 'when header does not exists' do + it 'returns nil' do + expect(headers[:accept]).to be_nil + end + end + + context 'when header has a single value' do + before { headers.set 'Content-Type', 'application/json' } + + it 'normalizes header name' do + expect(headers[:content_type]).to_not be_nil + end + + it 'returns it returns a single value' do + expect(headers[:content_type]).to eq 'application/json' + end + end + + context 'when header has a multiple values' do + before do + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, 'woo=hoo' + end + + it 'normalizes header name' do + expect(headers[:set_cookie]).to_not be_nil + end + + it 'returns array of associated values' do + expect(headers[:set_cookie]).to eq %w[hoo=ray woo=hoo] + end + end + end + + describe '#to_h' do + before do + headers.add :content_type, 'application/json' + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, 'woo=hoo' + end + + it 'returns a Hash' do + expect(headers.to_h).to be_a Hash + end + + it 'returns Hash with normalized keys' do + expect(headers.to_h.keys).to match_array %w[Content-Type Set-Cookie] + end + + context 'for a header with single value' do + it 'provides a value as is' do + expect(headers.to_h['Content-Type']).to eq 'application/json' + end + end + + context 'for a header with multiple values' do + it 'provides an array of values' do + expect(headers.to_h['Set-Cookie']).to eq %w[hoo=ray woo=hoo] + end + end + end + + describe '#to_a' do + before do + headers.add :content_type, 'application/json' + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, 'woo=hoo' + end + + it 'returns an Array' do + expect(headers.to_a).to be_a Array + end + + it 'returns Array of key/value pairs with normalized keys' do + expect(headers.to_a).to eq [ + %w[Content-Type application/json], + %w[Set-Cookie hoo=ray], + %w[Set-Cookie woo=hoo] + ] + end + end + + describe '#inspect' do + before { headers.set :set_cookie, %w[hoo=ray woo=hoo] } + subject { headers.inspect } + + it { should eq '#["hoo=ray", "woo=hoo"]}>' } + end + + describe '#keys' do + before do + headers.add :content_type, 'application/json' + headers.add :set_cookie, 'hoo=ray' + headers.add :set_cookie, 'woo=hoo' + end + + it 'returns uniq keys only' do + expect(headers.keys).to have_exactly(2).items + end + + it 'normalizes keys' do + expect(headers.keys).to include('Content-Type', 'Set-Cookie') + end + end + + describe '#each' do + before do + headers.add :set_cookie, 'hoo=ray' + headers.add :content_type, 'application/json' + headers.add :set_cookie, 'woo=hoo' + end + + it 'yields each key/value pair separatedly' do + expect { |b| headers.each(&b) }.to yield_control.exactly(3).times + end + + it 'yields headers in the same order they were added' do + expect { |b| headers.each(&b) }.to yield_successive_args( + %w[Set-Cookie hoo=ray], + %w[Content-Type application/json], + %w[Set-Cookie woo=hoo] + ) + end + end + + describe '.empty?' do + subject { headers.empty? } + + context 'initially' do + it { should be_true } + end + + context 'when header exists' do + before { headers.add :accept, 'text/plain' } + it { should be_false } + end + + context 'when last header was removed' do + before do + headers.add :accept, 'text/plain' + headers.delete :accept + end + + it { should be_true } + end + end + + describe '#hash' do + let(:left) { described_class.new } + let(:right) { described_class.new } + + it 'equals if two headers equals' do + left.add :accept, 'text/plain' + right.add :accept, 'text/plain' + + expect(left.hash).to eq right.hash + end + end + + describe '#==' do + let(:left) { described_class.new } + let(:right) { described_class.new } + + it 'compares header keys and values' do + left.add :accept, 'text/plain' + right.add :accept, 'text/plain' + + expect(left).to eq right + end + + it 'allows comparison with Array of key/value pairs' do + left.add :accept, 'text/plain' + expect(left).to eq [%w[Accept text/plain]] + end + + it 'sensitive to headers order' do + left.add :accept, 'text/plain' + left.add :cookie, 'woo=hoo' + right.add :cookie, 'woo=hoo' + right.add :accept, 'text/plain' + + expect(left).to_not eq right + end + + it 'sensitive to header values order' do + left.add :cookie, 'hoo=ray' + left.add :cookie, 'woo=hoo' + right.add :cookie, 'woo=hoo' + right.add :cookie, 'hoo=ray' + + expect(left).to_not eq right + end + end + + describe '#dup' do + before { headers.set :content_type, 'application/json' } + + subject(:dupped) { headers.dup } + + it { should be_a described_class } + it { should_not be headers } + + it 'has headers copied' do + expect(dupped[:content_type]).to eq 'application/json' + end + + context 'modifying a copy' do + before { dupped.set :content_type, 'text/plain' } + + it 'modifies dupped copy' do + expect(dupped[:content_type]).to eq 'text/plain' + end + + it 'does not affects original headers' do + expect(headers[:content_type]).to eq 'application/json' + end + end + end + + describe '#merge!' do + before do + headers.set :host, 'example.com' + headers.set :accept, 'application/json' + headers.merge! :accept => 'plain/text', :cookie => %w[hoo=ray woo=hoo] + end + + it 'leaves headers not presented in other as is' do + expect(headers[:host]).to eq 'example.com' + end + + it 'overwrites existing values' do + expect(headers[:accept]).to eq 'plain/text' + end + + it 'appends other headers, not presented in base' do + expect(headers[:cookie]).to eq %w[hoo=ray woo=hoo] + end + end + + describe '#merge' do + before do + headers.set :host, 'example.com' + headers.set :accept, 'application/json' + end + + subject(:merged) do + headers.merge :accept => 'plain/text', :cookie => %w[hoo=ray woo=hoo] + end + + it { should be_a described_class } + it { should_not be headers } + + it 'does not affects original headers' do + expect(merged.to_h).to_not eq headers.to_h + end + + it 'leaves headers not presented in other as is' do + expect(merged[:host]).to eq 'example.com' + end + + it 'overwrites existing values' do + expect(merged[:accept]).to eq 'plain/text' + end + + it 'appends other headers, not presented in base' do + expect(merged[:cookie]).to eq %w[hoo=ray woo=hoo] + end + end + + describe '.coerce' do + let(:dummyClass) { Class.new { def respond_to?(*); end } } + + it 'accepts any object that respond to #to_hash' do + hashie = double :to_hash => {'accept' => 'json'} + expect(described_class.coerce(hashie)['accept']).to eq 'json' + end + + it 'accepts any object that respond to #to_h' do + hashie = double :to_h => {'accept' => 'json'} + expect(described_class.coerce(hashie)['accept']).to eq 'json' + end + + it 'accepts any object that respond to #to_a' do + hashie = double :to_a => [%w[accept json]] + expect(described_class.coerce(hashie)['accept']).to eq 'json' + end + + it 'fails if given object cannot be coerced' do + expect { described_class.coerce dummyClass.new }.to raise_error HTTP::Error + end + + context 'with duplicate header keys (mixed case)' do + let(:headers) { {'Set-Cookie' => 'hoo=ray', 'set-cookie' => 'woo=hoo'} } + + it 'adds all headers' do + expect(described_class.coerce(headers).to_a).to match_array([ + %w[Set-Cookie hoo=ray], + %w[Set-Cookie woo=hoo] + ]) + end + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/body_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/body_spec.rb new file mode 100644 index 0000000..f25c8bb --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/body_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe HTTP::Options, 'body' do + + let(:opts) { HTTP::Options.new } + + it 'defaults to nil' do + expect(opts.body).to be nil + end + + it 'may be specified with with_body' do + opts2 = opts.with_body('foo') + expect(opts.body).to be nil + expect(opts2.body).to eq('foo') + end + +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/form_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/form_spec.rb new file mode 100644 index 0000000..61d1de5 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/form_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe HTTP::Options, 'form' do + + let(:opts) { HTTP::Options.new } + + it 'defaults to nil' do + expect(opts.form).to be nil + end + + it 'may be specified with with_form_data' do + opts2 = opts.with_form(:foo => 42) + expect(opts.form).to be nil + expect(opts2.form).to eq(:foo => 42) + end + +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/headers_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/headers_spec.rb new file mode 100644 index 0000000..5996445 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/headers_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe HTTP::Options, 'headers' do + + let(:opts) { HTTP::Options.new } + + it 'defaults to be empty' do + expect(opts.headers).to be_empty + end + + it 'may be specified with with_headers' do + opts2 = opts.with_headers('accept' => 'json') + expect(opts.headers).to be_empty + expect(opts2.headers).to eq([%w[Accept json]]) + end + + it 'accepts any object that respond to :to_hash' do + x = Struct.new(:to_hash).new('accept' => 'json') + expect(opts.with_headers(x).headers['accept']).to eq('json') + end + +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/json_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/json_spec.rb new file mode 100644 index 0000000..54f4776 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/json_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe HTTP::Options, 'json' do + + let(:opts) { HTTP::Options.new } + + it 'defaults to nil' do + expect(opts.json).to be nil + end + + it 'may be specified with with_json data' do + opts2 = opts.with_json(:foo => 42) + expect(opts.json).to be nil + expect(opts2.json).to eq(:foo => 42) + end + +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/merge_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/merge_spec.rb new file mode 100644 index 0000000..a721f4f --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/merge_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe HTTP::Options, 'merge' do + let(:opts) { HTTP::Options.new } + + it 'supports a Hash' do + old_response = opts.response + expect(opts.merge(:response => :body).response).to eq(:body) + expect(opts.response).to eq(old_response) + end + + it 'supports another Options' do + merged = opts.merge(HTTP::Options.new(:response => :body)) + expect(merged.response).to eq(:body) + end + + it 'merges as excepted in complex cases' do + # FIXME: yuck :( + + foo = HTTP::Options.new( + :response => :body, + :params => {:baz => 'bar'}, + :form => {:foo => 'foo'}, + :body => 'body-foo', + :json => {:foo => 'foo'}, + :headers => {:accept => 'json', :foo => 'foo'}, + :proxy => {}) + + bar = HTTP::Options.new( + :response => :parsed_body, + :params => {:plop => 'plip'}, + :form => {:bar => 'bar'}, + :body => 'body-bar', + :json => {:bar => 'bar'}, + :headers => {:accept => 'xml', :bar => 'bar'}, + :proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080}) + + expect(foo.merge(bar).to_hash).to eq( + :response => :parsed_body, + :params => {:plop => 'plip'}, + :form => {:bar => 'bar'}, + :body => 'body-bar', + :json => {:bar => 'bar'}, + :headers => {'Accept' => 'xml', 'Foo' => 'foo', 'Bar' => 'bar'}, + :proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080}, + :follow => nil, + :socket_class => described_class.default_socket_class, + :ssl_socket_class => described_class.default_ssl_socket_class, + :ssl_context => nil) + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/new_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/new_spec.rb new file mode 100644 index 0000000..845b447 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/new_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe HTTP::Options, 'new' do + it 'supports a Options instance' do + opts = HTTP::Options.new + expect(HTTP::Options.new(opts)).to eq(opts) + end + + context 'with a Hash' do + it 'coerces :response correctly' do + opts = HTTP::Options.new(:response => :object) + expect(opts.response).to eq(:object) + end + + it 'coerces :headers correctly' do + opts = HTTP::Options.new(:headers => {:accept => 'json'}) + expect(opts.headers).to eq([%w[Accept json]]) + end + + it 'coerces :proxy correctly' do + opts = HTTP::Options.new(:proxy => {:proxy_address => '127.0.0.1', :proxy_port => 8080}) + expect(opts.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080) + end + + it 'coerces :form correctly' do + opts = HTTP::Options.new(:form => {:foo => 42}) + expect(opts.form).to eq(:foo => 42) + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/options/proxy_spec.rb b/.gems/gems/http-0.6.2/spec/http/options/proxy_spec.rb new file mode 100644 index 0000000..b1c9e37 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options/proxy_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe HTTP::Options, 'proxy' do + + let(:opts) { HTTP::Options.new } + + it 'defaults to {}' do + expect(opts.proxy).to eq({}) + end + + it 'may be specified with with_proxy' do + opts2 = opts.with_proxy(:proxy_address => '127.0.0.1', :proxy_port => 8080) + expect(opts.proxy).to eq({}) + expect(opts2.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080) + end + + it 'accepts proxy address, port, username, and password' do + opts2 = opts.with_proxy(:proxy_address => '127.0.0.1', :proxy_port => 8080, :proxy_username => 'username', :proxy_password => 'password') + expect(opts2.proxy).to eq(:proxy_address => '127.0.0.1', :proxy_port => 8080, :proxy_username => 'username', :proxy_password => 'password') + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/options_spec.rb b/.gems/gems/http-0.6.2/spec/http/options_spec.rb new file mode 100644 index 0000000..f2bf76b --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/options_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe HTTP::Options do + subject { described_class.new(:response => :body) } + + it 'behaves like a Hash for reading' do + expect(subject[:response]).to eq(:body) + expect(subject[:nosuchone]).to be nil + end + + it 'coerces to a Hash' do + expect(subject.to_hash).to be_a(Hash) + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/redirector_spec.rb b/.gems/gems/http-0.6.2/spec/http/redirector_spec.rb new file mode 100644 index 0000000..b40018b --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/redirector_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe HTTP::Redirector do + def simple_response(status, body = '', headers = {}) + HTTP::Response.new(status, '1.1', headers, body) + end + + def redirect_response(location, status) + simple_response status, '', 'Location' => location + end + + let(:max_hops) { 5 } + subject(:redirector) { described_class.new max_hops } + + context 'following 300 redirect' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 300 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be orig_request.verb + simple_response 200 + end + end + end + + context 'following 301 redirect' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 301 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be orig_request.verb + simple_response 200 + end + end + end + + context 'following 302 redirect' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 302 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be orig_request.verb + simple_response 200 + end + end + end + + context 'following 303 redirect' do + context 'upon POST request' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 303 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be :get + simple_response 200 + end + end + end + + context 'upon HEAD request' do + let(:orig_request) { HTTP::Request.new :head, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 303 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be :get + simple_response 200 + end + end + end + end + + context 'following 307 redirect' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 307 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be orig_request.verb + simple_response 200 + end + end + end + + context 'following 308 redirect' do + let(:orig_request) { HTTP::Request.new :post, 'http://www.example.com/' } + let(:orig_response) { redirect_response 'http://example.com/', 308 } + + it 'follows without changing verb' do + redirector.perform(orig_request, orig_response) do |request| + expect(request.verb).to be orig_request.verb + simple_response 200 + end + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/request/writer_spec.rb b/.gems/gems/http-0.6.2/spec/http/request/writer_spec.rb new file mode 100644 index 0000000..026dbdd --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/request/writer_spec.rb @@ -0,0 +1,43 @@ +# coding: utf-8 + +require 'spec_helper' + +describe HTTP::Request::Writer do + describe '#initalize' do + def construct(body) + HTTP::Request::Writer.new(nil, body, [], '') + end + + it "doesn't throw on a nil body" do + expect { construct nil }.not_to raise_error + end + + it "doesn't throw on a String body" do + expect { construct 'string body' }.not_to raise_error + end + + it "doesn't throw on an Enumerable body" do + expect { construct %w[bees cows] }.not_to raise_error + end + + it "does throw on a body that isn't string, enumerable or nil" do + expect { construct true }.to raise_error + end + + it 'writes a chunked request from an Enumerable correctly' do + io = StringIO.new + writer = HTTP::Request::Writer.new(io, %w[bees cows], [], '') + writer.send_request_body + io.rewind + expect(io.string).to eq "4\r\nbees\r\n4\r\ncows\r\n0\r\n\r\n" + end + end + + describe '#add_body_type_headers' do + it 'properly calculates length of unicode string' do + writer = HTTP::Request::Writer.new(nil, 'Привет, мир!', {}, '') + writer.add_body_type_headers + expect(writer.join_headers).to match(/\r\nContent-Length: 21\r\n/) + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/request_spec.rb b/.gems/gems/http-0.6.2/spec/http/request_spec.rb new file mode 100644 index 0000000..4963f18 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/request_spec.rb @@ -0,0 +1,147 @@ +require 'spec_helper' + +describe HTTP::Request do + let(:headers) { {:accept => 'text/html'} } + let(:request_uri) { 'http://example.com/' } + + subject(:request) { HTTP::Request.new(:get, request_uri, headers) } + + it 'includes HTTP::Headers::Mixin' do + expect(described_class).to include HTTP::Headers::Mixin + end + + it 'requires URI to have scheme part' do + expect { HTTP::Request.new(:get, 'example.com/') }.to \ + raise_error(HTTP::Request::UnsupportedSchemeError) + end + + it 'provides a #scheme accessor' do + expect(request.scheme).to eq(:http) + end + + it 'sets given headers' do + expect(subject['Accept']).to eq('text/html') + end + + describe 'Host header' do + subject { request['Host'] } + + context 'was not given' do + it { is_expected.to eq 'example.com' } + + context 'and request URI has non-standard port' do + let(:request_uri) { 'http://example.com:3000/' } + it { is_expected.to eq 'example.com:3000' } + end + end + + context 'was explicitly given' do + before { headers[:host] = 'github.com' } + it { is_expected.to eq 'github.com' } + end + end + + describe 'User-Agent header' do + subject { request['User-Agent'] } + + context 'was not given' do + it { is_expected.to eq HTTP::Request::USER_AGENT } + end + + context 'was explicitly given' do + before { headers[:user_agent] = 'MrCrawly/123' } + it { is_expected.to eq 'MrCrawly/123' } + end + end + + it 'provides a #verb accessor' do + expect(subject.verb).to eq(:get) + end + + it 'provides a #method accessor that outputs a deprecation warning and returns the verb' do + warning = capture_warning do + expect(subject.method).to eq(subject.verb) + end + expect(warning).to match(/\[DEPRECATION\] HTTP::Request#method is deprecated\. Use #verb instead\. For Object#method, use #__method__\.$/) + end + + it 'provides a #__method__ method that delegates to Object#method' do + expect(subject.__method__(:verb)).to be_a(Method) + end + + describe '#redirect' do + let(:headers) { {:accept => 'text/html'} } + let(:proxy) { {:proxy_username => 'douglas', :proxy_password => 'adams'} } + let(:body) { 'The Ultimate Question' } + let(:request) { HTTP::Request.new(:post, 'http://example.com/', headers, proxy, body) } + + subject(:redirected) { request.redirect 'http://blog.example.com/' } + + its(:uri) { should eq URI.parse 'http://blog.example.com/' } + + its(:verb) { should eq request.verb } + its(:body) { should eq request.body } + its(:proxy) { should eq request.proxy } + + it 'presets new Host header' do + expect(redirected['Host']).to eq 'blog.example.com' + end + + context 'with schema-less absolute URL given' do + subject(:redirected) { request.redirect '//another.example.com/blog' } + + its(:uri) { should eq URI.parse 'http://another.example.com/blog' } + + its(:verb) { should eq request.verb } + its(:body) { should eq request.body } + its(:proxy) { should eq request.proxy } + + it 'presets new Host header' do + expect(redirected['Host']).to eq 'another.example.com' + end + end + + context 'with relative URL given' do + subject(:redirected) { request.redirect '/blog' } + + its(:uri) { should eq URI.parse 'http://example.com/blog' } + + its(:verb) { should eq request.verb } + its(:body) { should eq request.body } + its(:proxy) { should eq request.proxy } + + it 'keeps Host header' do + expect(redirected['Host']).to eq 'example.com' + end + + context 'with original URI having non-standard port' do + let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) } + its(:uri) { should eq URI.parse 'http://example.com:8080/blog' } + end + end + + context 'with relative URL that misses leading slash given' do + subject(:redirected) { request.redirect 'blog' } + + its(:uri) { should eq URI.parse 'http://example.com/blog' } + + its(:verb) { should eq request.verb } + its(:body) { should eq request.body } + its(:proxy) { should eq request.proxy } + + it 'keeps Host header' do + expect(redirected['Host']).to eq 'example.com' + end + + context 'with original URI having non-standard port' do + let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) } + its(:uri) { should eq URI.parse 'http://example.com:8080/blog' } + end + end + + context 'with new verb given' do + subject { request.redirect 'http://blog.example.com/', :get } + its(:verb) { should be :get } + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/response/body_spec.rb b/.gems/gems/http-0.6.2/spec/http/response/body_spec.rb new file mode 100644 index 0000000..fb5989c --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/response/body_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe HTTP::Response::Body do + let(:client) { double } + let(:chunks) { ['Hello, ', 'World!'] } + + before { allow(client).to receive(:readpartial) { chunks.shift } } + + subject(:body) { described_class.new client } + + it 'streams bodies from responses' do + expect(subject.to_s).to eq 'Hello, World!' + end + + context 'when body empty' do + let(:chunks) { [''] } + + it 'returns responds to empty? with true' do + expect(subject).to be_empty + end + end + + describe '#readpartial' do + context 'with size given' do + it 'passes value to underlying client' do + expect(client).to receive(:readpartial).with(42) + body.readpartial 42 + end + end + + context 'without size given' do + it 'does not blows up' do + expect { body.readpartial }.to_not raise_error + end + + it 'calls underlying client readpartial without specific size' do + expect(client).to receive(:readpartial).with no_args + body.readpartial + end + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http/response_spec.rb b/.gems/gems/http-0.6.2/spec/http/response_spec.rb new file mode 100644 index 0000000..c554cc1 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http/response_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe HTTP::Response do + it 'includes HTTP::Headers::Mixin' do + expect(described_class).to include HTTP::Headers::Mixin + end + + describe 'to_a' do + let(:body) { 'Hello world' } + let(:content_type) { 'text/plain' } + subject { HTTP::Response.new(200, '1.1', {'Content-Type' => content_type}, body) } + + it 'returns a Rack-like array' do + expect(subject.to_a).to eq([200, {'Content-Type' => content_type}, body]) + end + end + + describe 'mime_type' do + subject { HTTP::Response.new(200, '1.1', headers, '').mime_type } + + context 'without Content-Type header' do + let(:headers) { {} } + it { should be_nil } + end + + context 'with Content-Type: text/html' do + let(:headers) { {'Content-Type' => 'text/html'} } + it { should eq 'text/html' } + end + + context 'with Content-Type: text/html; charset=utf-8' do + let(:headers) { {'Content-Type' => 'text/html; charset=utf-8'} } + it { should eq 'text/html' } + end + end + + describe 'charset' do + subject { HTTP::Response.new(200, '1.1', headers, '').charset } + + context 'without Content-Type header' do + let(:headers) { {} } + it { should be_nil } + end + + context 'with Content-Type: text/html' do + let(:headers) { {'Content-Type' => 'text/html'} } + it { should be_nil } + end + + context 'with Content-Type: text/html; charset=utf-8' do + let(:headers) { {'Content-Type' => 'text/html; charset=utf-8'} } + it { should eq 'utf-8' } + end + end + + describe '#parse' do + let(:headers) { {'Content-Type' => content_type} } + let(:body) { '{"foo":"bar"}' } + let(:response) { HTTP::Response.new 200, '1.1', headers, body } + + context 'with known content type' do + let(:content_type) { 'application/json' } + it 'returns parsed body' do + expect(response.parse).to eq 'foo' => 'bar' + end + end + + context 'with unknown content type' do + let(:content_type) { 'application/deadbeef' } + it 'raises HTTP::Error' do + expect { response.parse }.to raise_error HTTP::Error + end + end + + context 'with explicitly given mime type' do + let(:content_type) { 'application/deadbeef' } + it 'ignores mime_type of response' do + expect(response.parse 'application/json').to eq 'foo' => 'bar' + end + + it 'supports MIME type aliases' do + expect(response.parse :json).to eq 'foo' => 'bar' + end + end + end + + describe '#flush' do + let(:body) { double :to_s => '' } + let(:response) { HTTP::Response.new 200, '1.1', {}, body } + + it 'returns response self-reference' do + expect(response.flush).to be response + end + + it 'flushes body' do + expect(body).to receive :to_s + response.flush + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/http_spec.rb b/.gems/gems/http-0.6.2/spec/http_spec.rb new file mode 100644 index 0000000..43ace3d --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/http_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' +require 'json' + +describe HTTP do + let(:test_endpoint) { "http://127.0.0.1:#{ExampleService::PORT}/" } + + context 'getting resources' do + it 'should be easy' do + response = HTTP.get test_endpoint + expect(response.to_s).to match(//) + end + + context 'with URI instance' do + it 'should be easy' do + response = HTTP.get URI(test_endpoint) + expect(response.to_s).to match(//) + end + end + + context 'with query string parameters' do + it 'is easy' do + response = HTTP.get "#{test_endpoint}params", :params => {:foo => 'bar'} + expect(response.to_s).to match(/Params!/) + end + end + + context 'with query string parameters in the URI and opts hash' do + it 'includes both' do + response = HTTP.get "#{test_endpoint}multiple-params?foo=bar", :params => {:baz => 'quux'} + expect(response.to_s).to match(/More Params!/) + end + end + + context 'with headers' do + it 'should be easy' do + response = HTTP.accept('application/json').get test_endpoint + expect(response.to_s.include?('json')).to be true + end + end + end + + context 'with http proxy address and port' do + it 'should proxy the request' do + response = HTTP.via('127.0.0.1', 8080).get test_endpoint + expect(response.headers['X-Proxied']).to eq 'true' + end + end + + context 'with http proxy address, port username and password' do + it 'should proxy the request' do + response = HTTP.via('127.0.0.1', 8081, 'username', 'password').get test_endpoint + expect(response.headers['X-Proxied']).to eq 'true' + end + + it 'responds with the endpoint\'s body' do + response = HTTP.via('127.0.0.1', 8081, 'username', 'password').get test_endpoint + expect(response.to_s).to match(//) + end + end + + context 'with http proxy address, port, with wrong username and password' do + it 'responds with 407' do + response = HTTP.via('127.0.0.1', 8081, 'user', 'pass').get test_endpoint + expect(response.status).to eq(407) + end + end + + context 'without proxy port' do + it 'should raise an argument error' do + expect { HTTP.via('127.0.0.1') }.to raise_error HTTP::RequestError + end + end + + context 'posting to resources' do + it 'should be easy to post forms' do + response = HTTP.post "#{test_endpoint}form", :form => {:example => 'testing-form'} + expect(response.to_s).to eq('passed :)') + end + end + + context 'posting with an explicit body' do + it 'should be easy to post' do + response = HTTP.post "#{test_endpoint}body", :body => 'testing-body' + expect(response.to_s).to eq('passed :)') + end + end + + context 'with redirects' do + it 'should be easy for 301' do + response = HTTP.with_follow(true).get("#{test_endpoint}redirect-301") + expect(response.to_s).to match(//) + end + + it 'should be easy for 302' do + response = HTTP.with_follow(true).get("#{test_endpoint}redirect-302") + expect(response.to_s).to match(//) + end + + end + + context 'head requests' do + it 'should be easy' do + response = HTTP.head test_endpoint + expect(response.status).to eq(200) + expect(response['content-type']).to match(/html/) + end + end + + describe '.auth' do + context 'with no arguments' do + specify { expect { HTTP.auth }.to raise_error } + end + + context 'with one argument' do + it 'returns branch with Authorization header as is' do + expect(HTTP).to receive(:with) \ + .with :authorization => 'foobar' + + HTTP.auth :foobar + end + end + + context 'with two arguments' do + it 'builds value with AuthorizationHeader builder' do + expect(HTTP::AuthorizationHeader).to receive(:build) \ + .with(:bearer, :token => 'token') + + HTTP.auth :bearer, :token => 'token' + end + end + + context 'with more than two arguments' do + specify { expect { HTTP.auth 1, 2, 3 }.to raise_error } + end + end +end diff --git a/.gems/gems/http-0.6.2/spec/spec_helper.rb b/.gems/gems/http-0.6.2/spec/spec_helper.rb new file mode 100644 index 0000000..d4fc208 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/spec_helper.rb @@ -0,0 +1,33 @@ +if RUBY_VERSION >= '1.9' + require 'simplecov' + require 'coveralls' + + SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] + + SimpleCov.start do + add_filter '/spec/' + minimum_coverage(80) + end +end + +require 'http' +require 'support/example_server' +require 'support/proxy_server' + +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = :expect + end +end + +def capture_warning + begin + old_stderr = $stderr + $stderr = StringIO.new + yield + result = $stderr.string + ensure + $stderr = old_stderr + end + result +end diff --git a/.gems/gems/http-0.6.2/spec/support/example_server.rb b/.gems/gems/http-0.6.2/spec/support/example_server.rb new file mode 100644 index 0000000..77b4d96 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/support/example_server.rb @@ -0,0 +1,102 @@ +require 'webrick' + +class ExampleService < WEBrick::HTTPServlet::AbstractServlet + PORT = 65432 # rubocop:disable NumericLiterals + + def do_GET(request, response) # rubocop:disable MethodName + case request.path + when '/' + handle_root(request, response) + when '/params' + handle_params(request, response) + when '/multiple-params' + handle_multiple_params(request, response) + when '/proxy' + response.status = 200 + response.body = 'Proxy!' + when '/not-found' + response.body = 'not found' + response.status = 404 + when '/redirect-301' + response.status = 301 + response['Location'] = "http://127.0.0.1:#{PORT}/" + when '/redirect-302' + response.status = 302 + response['Location'] = "http://127.0.0.1:#{PORT}/" + else + response.status = 404 + end + end + + def handle_root(request, response) + response.status = 200 + case request['Accept'] + when 'application/json' + response['Content-Type'] = 'application/json' + response.body = '{"json": true}' + else + response['Content-Type'] = 'text/html' + response.body = '' + end + end + + def handle_params(request, response) + return unless request.query_string == 'foo=bar' + + response.status = 200 + response.body = 'Params!' + end + + def handle_multiple_params(request, response) + params = CGI.parse(request.query_string) + + return unless params == {'foo' => ['bar'], 'baz' => ['quux']} + + response.status = 200 + response.body = 'More Params!' + end + + def do_POST(request, response) # rubocop:disable MethodName + case request.path + when '/form' + if request.query['example'] == 'testing-form' + response.status = 200 + response.body = 'passed :)' + else + response.status = 400 + response.body = 'invalid! >:E' + end + when '/body' + if request.body == 'testing-body' + response.status = 200 + response.body = 'passed :)' + else + response.status = 400 + response.body = 'invalid! >:E' + end + else + response.status = 404 + end + end + + def do_HEAD(request, response) # rubocop:disable MethodName + case request.path + when '/' + response.status = 200 + response['Content-Type'] = 'text/html' + else + response.status = 404 + end + end +end + +ExampleServer = WEBrick::HTTPServer.new(:Port => ExampleService::PORT, :AccessLog => []) +ExampleServer.mount '/', ExampleService + +t = Thread.new { ExampleServer.start } +trap('INT') do + ExampleServer.shutdown + exit +end + +Thread.pass while t.status && t.status != 'sleep' diff --git a/.gems/gems/http-0.6.2/spec/support/proxy_server.rb b/.gems/gems/http-0.6.2/spec/support/proxy_server.rb new file mode 100644 index 0000000..d990926 --- /dev/null +++ b/.gems/gems/http-0.6.2/spec/support/proxy_server.rb @@ -0,0 +1,31 @@ +require 'webrick/httpproxy' + +handler = proc { |_, res| res['X-PROXIED'] = true } + +ProxyServer = WEBrick::HTTPProxyServer.new( + :Port => 8080, + :AccessLog => [], + :RequestCallback => handler +) + +AuthenticatedProxyServer = WEBrick::HTTPProxyServer.new( + :Port => 8081, + :ProxyAuthProc => proc do | req, res | + WEBrick::HTTPAuth.proxy_basic_auth(req, res, 'proxy') do | user, pass | + user == 'username' && pass == 'password' + end + end, + :RequestCallback => handler +) + +Thread.new { ProxyServer.start } +trap('INT') do + ProxyServer.shutdown + exit +end + +Thread.new { AuthenticatedProxyServer.start } +trap('INT') do + AuthenticatedProxyServer.shutdown + exit +end diff --git a/.gems/gems/http_parser.rb-0.6.0/.gitignore b/.gems/gems/http_parser.rb-0.6.0/.gitignore new file mode 100644 index 0000000..d20f94b --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/.gitignore @@ -0,0 +1,11 @@ +tmp +*.bundle +*.gem +*.o +*.so +*.bundle +*.jar +*.swp +Makefile +tags +*.rbc diff --git a/.gems/gems/http_parser.rb-0.6.0/.gitmodules b/.gems/gems/http_parser.rb-0.6.0/.gitmodules new file mode 100644 index 0000000..6c289a3 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/.gitmodules @@ -0,0 +1,6 @@ +[submodule "http-parser"] + path = ext/ruby_http_parser/vendor/http-parser + url = git://github.com/joyent/http-parser.git +[submodule "http-parser-java"] + path = ext/ruby_http_parser/vendor/http-parser-java + url = git://github.com/tmm1/http-parser.java diff --git a/.gems/gems/http_parser.rb-0.6.0/Gemfile b/.gems/gems/http_parser.rb-0.6.0/Gemfile new file mode 100644 index 0000000..851fabc --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gemspec diff --git a/.gems/gems/http_parser.rb-0.6.0/Gemfile.lock b/.gems/gems/http_parser.rb-0.6.0/Gemfile.lock new file mode 100644 index 0000000..c880187 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/Gemfile.lock @@ -0,0 +1,39 @@ +PATH + remote: . + specs: + http_parser.rb (0.6.0.beta.2) + +GEM + remote: https://rubygems.org/ + specs: + benchmark_suite (0.8.0) + diff-lcs (1.1.2) + ffi (1.0.11) + ffi (1.0.11-java) + json (1.8.0) + json (1.8.0-java) + rake (0.9.2) + rake-compiler (0.7.9) + rake + rspec (2.4.0) + rspec-core (~> 2.4.0) + rspec-expectations (~> 2.4.0) + rspec-mocks (~> 2.4.0) + rspec-core (2.4.0) + rspec-expectations (2.4.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.4.0) + yajl-ruby (1.1.0) + +PLATFORMS + java + ruby + +DEPENDENCIES + benchmark_suite + ffi + http_parser.rb! + json (>= 1.4.6) + rake-compiler (>= 0.7.9) + rspec (>= 2.0.1) + yajl-ruby (>= 0.8.1) diff --git a/.gems/gems/http_parser.rb-0.6.0/LICENSE-MIT b/.gems/gems/http_parser.rb-0.6.0/LICENSE-MIT new file mode 100644 index 0000000..35f0bf0 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright 2009,2010 Marc-André Cournoyer +Copyright 2010,2011 Aman Gupta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/.gems/gems/http_parser.rb-0.6.0/README.md b/.gems/gems/http_parser.rb-0.6.0/README.md new file mode 100644 index 0000000..35dad45 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/README.md @@ -0,0 +1,90 @@ +# http_parser.rb + +A simple callback-based HTTP request/response parser for writing http +servers, clients and proxies. + +This gem is built on top of [joyent/http-parser](http://github.com/joyent/http-parser) and its java port [http-parser/http-parser.java](http://github.com/http-parser/http-parser.java). + +## Supported Platforms + +This gem aims to work on all major Ruby platforms, including: + +- MRI 1.8 and 1.9 +- Rubinius +- JRuby +- win32 + +## Usage + +```ruby +require "http/parser" + +parser = Http::Parser.new + +parser.on_headers_complete = proc do + p parser.http_version + + p parser.http_method # for requests + p parser.request_url + + p parser.status_code # for responses + + p parser.headers +end + +parser.on_body = proc do |chunk| + # One chunk of the body + p chunk +end + +parser.on_message_complete = proc do |env| + # Headers and body is all parsed + puts "Done!" +end +``` + +# Feed raw data from the socket to the parser +`parser << raw_data` + +## Advanced Usage + +### Accept callbacks on an object + +```ruby +module MyHttpConnection + def connection_completed + @parser = Http::Parser.new(self) + end + + def receive_data(data) + @parser << data + end + + def on_message_begin + @headers = nil + @body = '' + end + + def on_headers_complete(headers) + @headers = headers + end + + def on_body(chunk) + @body << chunk + end + + def on_message_complete + p [@headers, @body] + end +end +``` + +### Stop parsing after headers + +```ruby +parser = Http::Parser.new +parser.on_headers_complete = proc{ :stop } + +offset = parser << request_data +body = request_data[offset..-1] +``` diff --git a/.gems/gems/http_parser.rb-0.6.0/Rakefile b/.gems/gems/http_parser.rb-0.6.0/Rakefile new file mode 100644 index 0000000..150f652 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/Rakefile @@ -0,0 +1,6 @@ +# load tasks +Dir['tasks/*.rake'].sort.each { |f| load f } + +# default task +task :compile => :submodules +task :default => [:compile, :spec] diff --git a/.gems/gems/http_parser.rb-0.6.0/bench/standalone.rb b/.gems/gems/http_parser.rb-0.6.0/bench/standalone.rb new file mode 100755 index 0000000..6b4dcb6 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/bench/standalone.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +$:.unshift File.dirname(__FILE__) + "/../lib" +require "rubygems" +require "http/parser" +require "benchmark/ips" + +request = <<-REQUEST +GET / HTTP/1.1 +Host: www.example.com +Connection: keep-alive +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Encoding: gzip,deflate,sdch +Accept-Language: en-US,en;q=0.8 +Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 + +REQUEST +request.gsub!(/\n/m, "\r\n") + +Benchmark.ips do |ips| + ips.report("instance") { Http::Parser.new } + ips.report("parsing") { Http::Parser.new << request } +end diff --git a/.gems/gems/http_parser.rb-0.6.0/bench/thin.rb b/.gems/gems/http_parser.rb-0.6.0/bench/thin.rb new file mode 100644 index 0000000..fe0dd6d --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/bench/thin.rb @@ -0,0 +1,58 @@ +$:.unshift File.dirname(__FILE__) + "/../lib" +require "rubygems" +require "thin_parser" +require "http_parser" +require "benchmark" +require "stringio" + +data = "POST /postit HTTP/1.1\r\n" + + "Host: localhost:3000\r\n" + + "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9\r\n" + + "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n" + + "Accept-Language: en-us,en;q=0.5\r\n" + + "Accept-Encoding: gzip,deflate\r\n" + + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + + "Keep-Alive: 300\r\n" + + "Connection: keep-alive\r\n" + + "Content-Type: text/html\r\n" + + "Content-Length: 37\r\n" + + "\r\n" + + "name=marc&email=macournoyer@gmail.com" + +def thin(data) + env = {"rack.input" => StringIO.new} + Thin::HttpParser.new.execute(env, data, 0) + env +end + +def http_parser(data) + body = StringIO.new + env = nil + + parser = HTTP::RequestParser.new + parser.on_headers_complete = proc { |e| env = e } + parser.on_body = proc { |c| body << c } + parser << data + + env["rack-input"] = body + env +end + +# p thin(data) +# p http_parser(data) + +TESTS = 30_000 +Benchmark.bmbm do |results| + results.report("thin:") { TESTS.times { thin data } } + results.report("http-parser:") { TESTS.times { http_parser data } } +end + +# On my MBP core duo 2.2Ghz +# Rehearsal ------------------------------------------------ +# thin: 1.470000 0.000000 1.470000 ( 1.474737) +# http-parser: 1.270000 0.020000 1.290000 ( 1.292758) +# --------------------------------------- total: 2.760000sec +# +# user system total real +# thin: 1.150000 0.030000 1.180000 ( 1.173767) +# http-parser: 1.250000 0.010000 1.260000 ( 1.263796) diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/.RUBYARCHDIR.time b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/.RUBYARCHDIR.time new file mode 100644 index 0000000..e69de29 diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/.gitignore b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/.gitignore new file mode 100644 index 0000000..cb899d1 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/.gitignore @@ -0,0 +1 @@ +ryah_http_parser.* diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/RubyHttpParserService.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/RubyHttpParserService.java new file mode 100644 index 0000000..2ea3e8e --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/RubyHttpParserService.java @@ -0,0 +1,18 @@ +import java.io.IOException; + +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.runtime.load.BasicLibraryService; + +import org.ruby_http_parser.*; + +public class RubyHttpParserService implements BasicLibraryService { + public boolean basicLoad(final Ruby runtime) throws IOException { + RubyModule mHTTP = runtime.defineModule("HTTP"); + RubyClass cParser = mHTTP.defineClassUnder("Parser", runtime.getObject(), RubyHttpParser.ALLOCATOR); + cParser.defineAnnotatedMethods(RubyHttpParser.class); + cParser.defineClassUnder("Error", runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator()); + return true; + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ext_help.h b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ext_help.h new file mode 100644 index 0000000..a919dff --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ext_help.h @@ -0,0 +1,18 @@ +#ifndef ext_help_h +#define ext_help_h + +#define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); +#define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); +#define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); + +/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */ +#ifndef RSTRING_PTR +#define RSTRING_PTR(s) (RSTRING(s)->ptr) +#endif + +/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */ +#ifndef RSTRING_LEN +#define RSTRING_LEN(s) (RSTRING(s)->len) +#endif + +#endif diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/extconf.rb b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/extconf.rb new file mode 100644 index 0000000..d2f6e51 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/extconf.rb @@ -0,0 +1,24 @@ +require 'mkmf' + +# check out code if it hasn't been already +if Dir[File.expand_path('../vendor/http-parser/*', __FILE__)].empty? + Dir.chdir(File.expand_path('../../../', __FILE__)) do + xsystem 'git submodule init' + xsystem 'git submodule update' + end +end + +# mongrel and http-parser both define http_parser_(init|execute), so we +# rename functions in http-parser before using them. +vendor_dir = File.expand_path('../vendor/http-parser/', __FILE__) +src_dir = File.expand_path('../', __FILE__) +%w[ http_parser.c http_parser.h ].each do |file| + File.open(File.join(src_dir, "ryah_#{file}"), 'w'){ |f| + f.write File.read(File.join(vendor_dir, file)).gsub('http_parser', 'ryah_http_parser') + } +end + +$CFLAGS << " -I#{src_dir}" + +dir_config("ruby_http_parser") +create_makefile("ruby_http_parser") diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java new file mode 100644 index 0000000..ac586a9 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java @@ -0,0 +1,495 @@ +package org.ruby_http_parser; + +import http_parser.HTTPException; +import http_parser.HTTPMethod; +import http_parser.HTTPParser; +import http_parser.lolevel.HTTPCallback; +import http_parser.lolevel.HTTPDataCallback; +import http_parser.lolevel.ParserSettings; + +import java.nio.ByteBuffer; + +import org.jcodings.Encoding; +import org.jcodings.specific.UTF8Encoding; +import org.jruby.Ruby; +import org.jruby.RubyArray; +import org.jruby.RubyClass; +import org.jruby.RubyHash; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.RubyString; +import org.jruby.RubySymbol; +import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.RaiseException; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.util.ByteList; + +public class RubyHttpParser extends RubyObject { + + @JRubyMethod(name = "strict?", module = true) + public static IRubyObject strict(IRubyObject recv) { + return recv.getRuntime().newBoolean(true); + } + + public static ObjectAllocator ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klass) { + return new RubyHttpParser(runtime, klass); + } + }; + + byte[] fetchBytes(ByteBuffer b, int pos, int len) { + byte[] by = new byte[len]; + int saved = b.position(); + b.position(pos); + b.get(by); + b.position(saved); + return by; + } + + public class StopException extends RuntimeException { + } + + private Ruby runtime; + private HTTPParser parser; + private ParserSettings settings; + + private RubyClass eParserError; + + private RubyHash headers; + + private IRubyObject on_message_begin; + private IRubyObject on_headers_complete; + private IRubyObject on_body; + private IRubyObject on_message_complete; + + private IRubyObject requestUrl; + private IRubyObject requestPath; + private IRubyObject queryString; + private IRubyObject fragment; + + private IRubyObject header_value_type; + private IRubyObject upgradeData; + + private IRubyObject callback_object; + + private boolean completed; + + private byte[] _current_header; + private byte[] _last_header; + + private static final Encoding UTF8 = UTF8Encoding.INSTANCE; + + public RubyHttpParser(final Ruby runtime, RubyClass clazz) { + super(runtime, clazz); + + this.runtime = runtime; + this.eParserError = (RubyClass) runtime.getModule("HTTP").getClass("Parser").getConstant("Error"); + + this.on_message_begin = null; + this.on_headers_complete = null; + this.on_body = null; + this.on_message_complete = null; + + this.callback_object = null; + + this.completed = false; + + this.header_value_type = runtime.getModule("HTTP").getClass("Parser") + .getInstanceVariable("@default_header_value_type"); + + initSettings(); + init(); + } + + private void initSettings() { + this.settings = new ParserSettings(); + + this.settings.on_url = new HTTPDataCallback() { + public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { + byte[] data = fetchBytes(buf, pos, len); + if (runtime.is1_9() || runtime.is2_0()) { + ((RubyString) requestUrl).cat(data, 0, data.length, UTF8); + } else { + ((RubyString) requestUrl).cat(data); + } + return 0; + } + }; + + this.settings.on_header_field = new HTTPDataCallback() { + public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { + byte[] data = fetchBytes(buf, pos, len); + + if (_current_header == null) + _current_header = data; + else { + byte[] tmp = new byte[_current_header.length + data.length]; + System.arraycopy(_current_header, 0, tmp, 0, _current_header.length); + System.arraycopy(data, 0, tmp, _current_header.length, data.length); + _current_header = tmp; + } + + return 0; + } + }; + final RubySymbol arraysSym = runtime.newSymbol("arrays"); + final RubySymbol mixedSym = runtime.newSymbol("mixed"); + final RubySymbol stopSym = runtime.newSymbol("stop"); + final RubySymbol resetSym = runtime.newSymbol("reset"); + this.settings.on_header_value = new HTTPDataCallback() { + public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { + byte[] data = fetchBytes(buf, pos, len); + ThreadContext context = headers.getRuntime().getCurrentContext(); + IRubyObject key, val; + int new_field = 0; + + if (_current_header != null) { + new_field = 1; + _last_header = _current_header; + _current_header = null; + } + + key = RubyString.newString(runtime, new ByteList(_last_header, UTF8, false)); + val = headers.op_aref(context, key); + + if (new_field == 1) { + if (val.isNil()) { + if (header_value_type == arraysSym) { + headers.op_aset(context, key, + RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10, UTF8))); + } else { + headers.op_aset(context, key, RubyString.newStringLight(runtime, 10, UTF8)); + } + } else { + if (header_value_type == mixedSym) { + if (val instanceof RubyString) { + headers.op_aset(context, key, + RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10, UTF8))); + } else { + ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8)); + } + } else if (header_value_type == arraysSym) { + ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8)); + } else { + if (runtime.is1_9() || runtime.is2_0()) { + ((RubyString) val).cat(',', UTF8).cat(' ', UTF8); + } else { + ((RubyString) val).cat(',').cat(' '); + } + } + } + val = headers.op_aref(context, key); + } + + if (val instanceof RubyArray) { + val = ((RubyArray) val).entry(-1); + } + + if (runtime.is1_9() || runtime.is2_0()) { + ((RubyString) val).cat(data, 0, data.length, UTF8); + } else { + ((RubyString) val).cat(data); + } + + return 0; + } + }; + + this.settings.on_message_begin = new HTTPCallback() { + public int cb(http_parser.lolevel.HTTPParser p) { + headers = new RubyHash(runtime); + + if (runtime.is1_9() || runtime.is2_0()) { + requestUrl = RubyString.newEmptyString(runtime, UTF8); + requestPath = RubyString.newEmptyString(runtime, UTF8); + queryString = RubyString.newEmptyString(runtime, UTF8); + fragment = RubyString.newEmptyString(runtime, UTF8); + upgradeData = RubyString.newEmptyString(runtime, UTF8); + } else { + requestUrl = RubyString.newEmptyString(runtime); + requestPath = RubyString.newEmptyString(runtime); + queryString = RubyString.newEmptyString(runtime); + fragment = RubyString.newEmptyString(runtime); + upgradeData = RubyString.newEmptyString(runtime); + } + + IRubyObject ret = runtime.getNil(); + + if (callback_object != null) { + if (((RubyObject) callback_object).respondsTo("on_message_begin")) { + ThreadContext context = callback_object.getRuntime().getCurrentContext(); + ret = callback_object.callMethod(context, "on_message_begin"); + } + } else if (on_message_begin != null) { + ThreadContext context = on_message_begin.getRuntime().getCurrentContext(); + ret = on_message_begin.callMethod(context, "call"); + } + + if (ret == stopSym) { + throw new StopException(); + } else { + return 0; + } + } + }; + this.settings.on_message_complete = new HTTPCallback() { + public int cb(http_parser.lolevel.HTTPParser p) { + IRubyObject ret = runtime.getNil(); + + completed = true; + + if (callback_object != null) { + if (((RubyObject) callback_object).respondsTo("on_message_complete")) { + ThreadContext context = callback_object.getRuntime().getCurrentContext(); + ret = callback_object.callMethod(context, "on_message_complete"); + } + } else if (on_message_complete != null) { + ThreadContext context = on_message_complete.getRuntime().getCurrentContext(); + ret = on_message_complete.callMethod(context, "call"); + } + + if (ret == stopSym) { + throw new StopException(); + } else { + return 0; + } + } + }; + this.settings.on_headers_complete = new HTTPCallback() { + public int cb(http_parser.lolevel.HTTPParser p) { + IRubyObject ret = runtime.getNil(); + + if (callback_object != null) { + if (((RubyObject) callback_object).respondsTo("on_headers_complete")) { + ThreadContext context = callback_object.getRuntime().getCurrentContext(); + ret = callback_object.callMethod(context, "on_headers_complete", headers); + } + } else if (on_headers_complete != null) { + ThreadContext context = on_headers_complete.getRuntime().getCurrentContext(); + ret = on_headers_complete.callMethod(context, "call", headers); + } + + if (ret == stopSym) { + throw new StopException(); + } else if (ret == resetSym) { + return 1; + } else { + return 0; + } + } + }; + this.settings.on_body = new HTTPDataCallback() { + public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { + IRubyObject ret = runtime.getNil(); + byte[] data = fetchBytes(buf, pos, len); + + if (callback_object != null) { + if (((RubyObject) callback_object).respondsTo("on_body")) { + ThreadContext context = callback_object.getRuntime().getCurrentContext(); + ret = callback_object.callMethod(context, "on_body", + RubyString.newString(runtime, new ByteList(data, UTF8, false))); + } + } else if (on_body != null) { + ThreadContext context = on_body.getRuntime().getCurrentContext(); + ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8, false))); + } + + if (ret == stopSym) { + throw new StopException(); + } else { + return 0; + } + } + }; + } + + private void init() { + this.parser = new HTTPParser(); + this.parser.HTTP_PARSER_STRICT = true; + this.headers = null; + + this.requestUrl = runtime.getNil(); + this.requestPath = runtime.getNil(); + this.queryString = runtime.getNil(); + this.fragment = runtime.getNil(); + + this.upgradeData = runtime.getNil(); + } + + @JRubyMethod(name = "initialize") + public IRubyObject initialize() { + return this; + } + + @JRubyMethod(name = "initialize") + public IRubyObject initialize(IRubyObject arg) { + callback_object = arg; + return initialize(); + } + + @JRubyMethod(name = "initialize") + public IRubyObject initialize(IRubyObject arg, IRubyObject arg2) { + header_value_type = arg2; + return initialize(arg); + } + + @JRubyMethod(name = "on_message_begin=") + public IRubyObject set_on_message_begin(IRubyObject cb) { + on_message_begin = cb; + return cb; + } + + @JRubyMethod(name = "on_headers_complete=") + public IRubyObject set_on_headers_complete(IRubyObject cb) { + on_headers_complete = cb; + return cb; + } + + @JRubyMethod(name = "on_body=") + public IRubyObject set_on_body(IRubyObject cb) { + on_body = cb; + return cb; + } + + @JRubyMethod(name = "on_message_complete=") + public IRubyObject set_on_message_complete(IRubyObject cb) { + on_message_complete = cb; + return cb; + } + + @JRubyMethod(name = "<<") + public IRubyObject execute(IRubyObject data) { + RubyString str = (RubyString) data; + ByteList byteList = str.getByteList(); + ByteBuffer buf = ByteBuffer.wrap(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize()); + boolean stopped = false; + + try { + this.parser.execute(this.settings, buf); + } catch (HTTPException e) { + throw new RaiseException(runtime, eParserError, e.getMessage(), true); + } catch (StopException e) { + stopped = true; + } + + if (parser.getUpgrade()) { + byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position()); + if (runtime.is1_9() || runtime.is2_0()) { + ((RubyString) upgradeData).cat(upData, 0, upData.length, UTF8); + } else { + ((RubyString) upgradeData).cat(upData); + } + } else if (buf.hasRemaining() && !completed) { + if (!stopped) + throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true); + } + + return RubyNumeric.int2fix(runtime, buf.position()); + } + + @JRubyMethod(name = "keep_alive?") + public IRubyObject shouldKeepAlive() { + return runtime.newBoolean(parser.shouldKeepAlive()); + } + + @JRubyMethod(name = "upgrade?") + public IRubyObject shouldUpgrade() { + return runtime.newBoolean(parser.getUpgrade()); + } + + @JRubyMethod(name = "http_major") + public IRubyObject httpMajor() { + if (parser.getMajor() == 0 && parser.getMinor() == 0) + return runtime.getNil(); + else + return RubyNumeric.int2fix(runtime, parser.getMajor()); + } + + @JRubyMethod(name = "http_minor") + public IRubyObject httpMinor() { + if (parser.getMajor() == 0 && parser.getMinor() == 0) + return runtime.getNil(); + else + return RubyNumeric.int2fix(runtime, parser.getMinor()); + } + + @JRubyMethod(name = "http_version") + public IRubyObject httpVersion() { + if (parser.getMajor() == 0 && parser.getMinor() == 0) + return runtime.getNil(); + else + return runtime.newArray(httpMajor(), httpMinor()); + } + + @JRubyMethod(name = "http_method") + public IRubyObject httpMethod() { + HTTPMethod method = parser.getHTTPMethod(); + if (method != null) + return runtime.newString(new String(method.bytes)); + else + return runtime.getNil(); + } + + @JRubyMethod(name = "status_code") + public IRubyObject statusCode() { + int code = parser.getStatusCode(); + if (code != 0) + return RubyNumeric.int2fix(runtime, code); + else + return runtime.getNil(); + } + + @JRubyMethod(name = "headers") + public IRubyObject getHeaders() { + return headers == null ? runtime.getNil() : headers; + } + + @JRubyMethod(name = "request_url") + public IRubyObject getRequestUrl() { + return requestUrl == null ? runtime.getNil() : requestUrl; + } + + @JRubyMethod(name = "request_path") + public IRubyObject getRequestPath() { + return requestPath == null ? runtime.getNil() : requestPath; + } + + @JRubyMethod(name = "query_string") + public IRubyObject getQueryString() { + return queryString == null ? runtime.getNil() : queryString; + } + + @JRubyMethod(name = "fragment") + public IRubyObject getFragment() { + return fragment == null ? runtime.getNil() : fragment; + } + + @JRubyMethod(name = "header_value_type") + public IRubyObject getHeaderValueType() { + return header_value_type == null ? runtime.getNil() : header_value_type; + } + + @JRubyMethod(name = "header_value_type=") + public IRubyObject set_header_value_type(IRubyObject val) { + String valString = val.toString(); + if (valString != "mixed" && valString != "arrays" && valString != "strings") { + throw runtime.newArgumentError("Invalid header value type"); + } + header_value_type = val; + return val; + } + + @JRubyMethod(name = "upgrade_data") + public IRubyObject upgradeData() { + return upgradeData == null ? runtime.getNil() : upgradeData; + } + + @JRubyMethod(name = "reset!") + public IRubyObject reset() { + init(); + return runtime.getTrue(); + } + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ruby_http_parser.c b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ruby_http_parser.c new file mode 100644 index 0000000..5650652 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/ruby_http_parser.c @@ -0,0 +1,515 @@ +#include "ruby.h" +#include "ext_help.h" +#include "ryah_http_parser.h" + +#define GET_WRAPPER(N, from) ParserWrapper *N = (ParserWrapper *)(from)->data; +#define HASH_CAT(h, k, ptr, len) \ + do { \ + VALUE __v = rb_hash_aref(h, k); \ + if (__v != Qnil) { \ + rb_str_cat(__v, ptr, len); \ + } else { \ + rb_hash_aset(h, k, rb_str_new(ptr, len)); \ + } \ + } while(0) + +typedef struct ParserWrapper { + ryah_http_parser parser; + + VALUE request_url; + + VALUE headers; + + VALUE upgrade_data; + + VALUE on_message_begin; + VALUE on_headers_complete; + VALUE on_body; + VALUE on_message_complete; + + VALUE callback_object; + VALUE stopped; + VALUE completed; + + VALUE header_value_type; + + VALUE last_field_name; + VALUE curr_field_name; + + enum ryah_http_parser_type type; +} ParserWrapper; + +void ParserWrapper_init(ParserWrapper *wrapper) { + ryah_http_parser_init(&wrapper->parser, wrapper->type); + wrapper->parser.status_code = 0; + wrapper->parser.http_major = 0; + wrapper->parser.http_minor = 0; + + wrapper->request_url = Qnil; + + wrapper->upgrade_data = Qnil; + + wrapper->headers = Qnil; + wrapper->completed = Qfalse; + + wrapper->last_field_name = Qnil; + wrapper->curr_field_name = Qnil; +} + +void ParserWrapper_mark(void *data) { + if(data) { + ParserWrapper *wrapper = (ParserWrapper *) data; + rb_gc_mark_maybe(wrapper->request_url); + rb_gc_mark_maybe(wrapper->upgrade_data); + rb_gc_mark_maybe(wrapper->headers); + rb_gc_mark_maybe(wrapper->on_message_begin); + rb_gc_mark_maybe(wrapper->on_headers_complete); + rb_gc_mark_maybe(wrapper->on_body); + rb_gc_mark_maybe(wrapper->on_message_complete); + rb_gc_mark_maybe(wrapper->callback_object); + rb_gc_mark_maybe(wrapper->last_field_name); + rb_gc_mark_maybe(wrapper->curr_field_name); + } +} + +void ParserWrapper_free(void *data) { + if(data) { + free(data); + } +} + +static VALUE cParser; +static VALUE cRequestParser; +static VALUE cResponseParser; + +static VALUE eParserError; + +static ID Icall; +static ID Ion_message_begin; +static ID Ion_headers_complete; +static ID Ion_body; +static ID Ion_message_complete; + +static VALUE Sstop; +static VALUE Sreset; +static VALUE Sarrays; +static VALUE Sstrings; +static VALUE Smixed; + +/** Callbacks **/ + +int on_message_begin(ryah_http_parser *parser) { + GET_WRAPPER(wrapper, parser); + + wrapper->request_url = rb_str_new2(""); + wrapper->headers = rb_hash_new(); + wrapper->upgrade_data = rb_str_new2(""); + + VALUE ret = Qnil; + + if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_begin)) { + ret = rb_funcall(wrapper->callback_object, Ion_message_begin, 0); + } else if (wrapper->on_message_begin != Qnil) { + ret = rb_funcall(wrapper->on_message_begin, Icall, 0); + } + + if (ret == Sstop) { + wrapper->stopped = Qtrue; + return -1; + } else { + return 0; + } +} + +int on_url(ryah_http_parser *parser, const char *at, size_t length) { + GET_WRAPPER(wrapper, parser); + rb_str_cat(wrapper->request_url, at, length); + return 0; +} + +int on_header_field(ryah_http_parser *parser, const char *at, size_t length) { + GET_WRAPPER(wrapper, parser); + + if (wrapper->curr_field_name == Qnil) { + wrapper->last_field_name = Qnil; + wrapper->curr_field_name = rb_str_new(at, length); + } else { + rb_str_cat(wrapper->curr_field_name, at, length); + } + + return 0; +} + +int on_header_value(ryah_http_parser *parser, const char *at, size_t length) { + GET_WRAPPER(wrapper, parser); + + int new_field = 0; + VALUE current_value; + + if (wrapper->last_field_name == Qnil) { + new_field = 1; + wrapper->last_field_name = wrapper->curr_field_name; + wrapper->curr_field_name = Qnil; + } + + current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name); + + if (new_field == 1) { + if (current_value == Qnil) { + if (wrapper->header_value_type == Sarrays) { + rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(1, rb_str_new2(""))); + } else { + rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_str_new2("")); + } + } else { + if (wrapper->header_value_type == Smixed) { + if (TYPE(current_value) == T_STRING) { + rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(2, current_value, rb_str_new2(""))); + } else { + rb_ary_push(current_value, rb_str_new2("")); + } + } else if (wrapper->header_value_type == Sarrays) { + rb_ary_push(current_value, rb_str_new2("")); + } else { + rb_str_cat(current_value, ", ", 2); + } + } + current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name); + } + + if (TYPE(current_value) == T_ARRAY) { + current_value = rb_ary_entry(current_value, -1); + } + + rb_str_cat(current_value, at, length); + + return 0; +} + +int on_headers_complete(ryah_http_parser *parser) { + GET_WRAPPER(wrapper, parser); + + VALUE ret = Qnil; + + if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_headers_complete)) { + ret = rb_funcall(wrapper->callback_object, Ion_headers_complete, 1, wrapper->headers); + } else if (wrapper->on_headers_complete != Qnil) { + ret = rb_funcall(wrapper->on_headers_complete, Icall, 1, wrapper->headers); + } + + if (ret == Sstop) { + wrapper->stopped = Qtrue; + return -1; + } else if (ret == Sreset){ + return 1; + } else { + return 0; + } +} + +int on_body(ryah_http_parser *parser, const char *at, size_t length) { + GET_WRAPPER(wrapper, parser); + + VALUE ret = Qnil; + + if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_body)) { + ret = rb_funcall(wrapper->callback_object, Ion_body, 1, rb_str_new(at, length)); + } else if (wrapper->on_body != Qnil) { + ret = rb_funcall(wrapper->on_body, Icall, 1, rb_str_new(at, length)); + } + + if (ret == Sstop) { + wrapper->stopped = Qtrue; + return -1; + } else { + return 0; + } +} + +int on_message_complete(ryah_http_parser *parser) { + GET_WRAPPER(wrapper, parser); + + VALUE ret = Qnil; + wrapper->completed = Qtrue; + + if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_complete)) { + ret = rb_funcall(wrapper->callback_object, Ion_message_complete, 0); + } else if (wrapper->on_message_complete != Qnil) { + ret = rb_funcall(wrapper->on_message_complete, Icall, 0); + } + + if (ret == Sstop) { + wrapper->stopped = Qtrue; + return -1; + } else { + return 0; + } +} + +static ryah_http_parser_settings settings = { + .on_message_begin = on_message_begin, + .on_url = on_url, + .on_header_field = on_header_field, + .on_header_value = on_header_value, + .on_headers_complete = on_headers_complete, + .on_body = on_body, + .on_message_complete = on_message_complete +}; + +VALUE Parser_alloc_by_type(VALUE klass, enum ryah_http_parser_type type) { + ParserWrapper *wrapper = ALLOC_N(ParserWrapper, 1); + wrapper->type = type; + wrapper->parser.data = wrapper; + + wrapper->on_message_begin = Qnil; + wrapper->on_headers_complete = Qnil; + wrapper->on_body = Qnil; + wrapper->on_message_complete = Qnil; + + wrapper->callback_object = Qnil; + + ParserWrapper_init(wrapper); + + return Data_Wrap_Struct(klass, ParserWrapper_mark, ParserWrapper_free, wrapper); +} + +VALUE Parser_alloc(VALUE klass) { + return Parser_alloc_by_type(klass, HTTP_BOTH); +} + +VALUE RequestParser_alloc(VALUE klass) { + return Parser_alloc_by_type(klass, HTTP_REQUEST); +} + +VALUE ResponseParser_alloc(VALUE klass) { + return Parser_alloc_by_type(klass, HTTP_RESPONSE); +} + +VALUE Parser_strict_p(VALUE klass) { + return HTTP_PARSER_STRICT == 1 ? Qtrue : Qfalse; +} + +VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type"); + + if (argc == 1) { + wrapper->callback_object = argv[0]; + } + + if (argc == 2) { + wrapper->callback_object = argv[0]; + wrapper->header_value_type = argv[1]; + } + + return self; +} + +VALUE Parser_execute(VALUE self, VALUE data) { + ParserWrapper *wrapper = NULL; + + Check_Type(data, T_STRING); + char *ptr = RSTRING_PTR(data); + long len = RSTRING_LEN(data); + + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->stopped = Qfalse; + size_t nparsed = ryah_http_parser_execute(&wrapper->parser, &settings, ptr, len); + + if (wrapper->parser.upgrade) { + if (RTEST(wrapper->stopped)) + nparsed += 1; + + rb_str_cat(wrapper->upgrade_data, ptr + nparsed, len - nparsed); + + } else if (nparsed != (size_t)len) { + if (!RTEST(wrapper->stopped) && !RTEST(wrapper->completed)) + rb_raise(eParserError, "Could not parse data entirely (%zu != %zu)", nparsed, len); + else + nparsed += 1; // error states fail on the current character + } + + return INT2FIX(nparsed); +} + +VALUE Parser_set_on_message_begin(VALUE self, VALUE callback) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->on_message_begin = callback; + return callback; +} + +VALUE Parser_set_on_headers_complete(VALUE self, VALUE callback) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->on_headers_complete = callback; + return callback; +} + +VALUE Parser_set_on_body(VALUE self, VALUE callback) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->on_body = callback; + return callback; +} + +VALUE Parser_set_on_message_complete(VALUE self, VALUE callback) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + wrapper->on_message_complete = callback; + return callback; +} + +VALUE Parser_keep_alive_p(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + return http_should_keep_alive(&wrapper->parser) == 1 ? Qtrue : Qfalse; +} + +VALUE Parser_upgrade_p(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + return wrapper->parser.upgrade ? Qtrue : Qfalse; +} + +VALUE Parser_http_version(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) + return Qnil; + else + return rb_ary_new3(2, INT2FIX(wrapper->parser.http_major), INT2FIX(wrapper->parser.http_minor)); +} + +VALUE Parser_http_major(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) + return Qnil; + else + return INT2FIX(wrapper->parser.http_major); +} + +VALUE Parser_http_minor(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) + return Qnil; + else + return INT2FIX(wrapper->parser.http_minor); +} + +VALUE Parser_http_method(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + if (wrapper->parser.type == HTTP_REQUEST) + return rb_str_new2(http_method_str(wrapper->parser.method)); + else + return Qnil; +} + +VALUE Parser_status_code(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + if (wrapper->parser.status_code) + return INT2FIX(wrapper->parser.status_code); + else + return Qnil; +} + +#define DEFINE_GETTER(name) \ + VALUE Parser_##name(VALUE self) { \ + ParserWrapper *wrapper = NULL; \ + DATA_GET(self, ParserWrapper, wrapper); \ + return wrapper->name; \ + } + +DEFINE_GETTER(request_url); +DEFINE_GETTER(headers); +DEFINE_GETTER(upgrade_data); +DEFINE_GETTER(header_value_type); + +VALUE Parser_set_header_value_type(VALUE self, VALUE val) { + if (val != Sarrays && val != Sstrings && val != Smixed) { + rb_raise(rb_eArgError, "Invalid header value type"); + } + + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + wrapper->header_value_type = val; + return wrapper->header_value_type; +} + +VALUE Parser_reset(VALUE self) { + ParserWrapper *wrapper = NULL; + DATA_GET(self, ParserWrapper, wrapper); + + ParserWrapper_init(wrapper); + + return Qtrue; +} + +void Init_ruby_http_parser() { + VALUE mHTTP = rb_define_module("HTTP"); + cParser = rb_define_class_under(mHTTP, "Parser", rb_cObject); + cRequestParser = rb_define_class_under(mHTTP, "RequestParser", cParser); + cResponseParser = rb_define_class_under(mHTTP, "ResponseParser", cParser); + + eParserError = rb_define_class_under(cParser, "Error", rb_eIOError); + Icall = rb_intern("call"); + Ion_message_begin = rb_intern("on_message_begin"); + Ion_headers_complete = rb_intern("on_headers_complete"); + Ion_body = rb_intern("on_body"); + Ion_message_complete = rb_intern("on_message_complete"); + Sstop = ID2SYM(rb_intern("stop")); + Sreset = ID2SYM(rb_intern("reset")); + + Sarrays = ID2SYM(rb_intern("arrays")); + Sstrings = ID2SYM(rb_intern("strings")); + Smixed = ID2SYM(rb_intern("mixed")); + + rb_define_alloc_func(cParser, Parser_alloc); + rb_define_alloc_func(cRequestParser, RequestParser_alloc); + rb_define_alloc_func(cResponseParser, ResponseParser_alloc); + + rb_define_singleton_method(cParser, "strict?", Parser_strict_p, 0); + rb_define_method(cParser, "initialize", Parser_initialize, -1); + + rb_define_method(cParser, "on_message_begin=", Parser_set_on_message_begin, 1); + rb_define_method(cParser, "on_headers_complete=", Parser_set_on_headers_complete, 1); + rb_define_method(cParser, "on_body=", Parser_set_on_body, 1); + rb_define_method(cParser, "on_message_complete=", Parser_set_on_message_complete, 1); + rb_define_method(cParser, "<<", Parser_execute, 1); + + rb_define_method(cParser, "keep_alive?", Parser_keep_alive_p, 0); + rb_define_method(cParser, "upgrade?", Parser_upgrade_p, 0); + + rb_define_method(cParser, "http_version", Parser_http_version, 0); + rb_define_method(cParser, "http_major", Parser_http_major, 0); + rb_define_method(cParser, "http_minor", Parser_http_minor, 0); + + rb_define_method(cParser, "http_method", Parser_http_method, 0); + rb_define_method(cParser, "status_code", Parser_status_code, 0); + + rb_define_method(cParser, "request_url", Parser_request_url, 0); + rb_define_method(cParser, "headers", Parser_headers, 0); + rb_define_method(cParser, "upgrade_data", Parser_upgrade_data, 0); + rb_define_method(cParser, "header_value_type", Parser_header_value_type, 0); + rb_define_method(cParser, "header_value_type=", Parser_set_header_value_type, 1); + + rb_define_method(cParser, "reset!", Parser_reset, 0); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/.gitkeep b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS new file mode 100644 index 0000000..abe99de --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS @@ -0,0 +1,32 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +LE ROUX Thomas +Randy Rizun diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT new file mode 100644 index 0000000..a0ae8dc --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT @@ -0,0 +1,48 @@ +Copyright 2010 Tim Becker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +--- END OF LICENSE + +This code mainly based on code with the following license: + + +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/README.md b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/README.md new file mode 100644 index 0000000..0a6a432 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/README.md @@ -0,0 +1,183 @@ +HTTP Parser +=========== + +This is a parser for HTTP written in Java, based quite heavily on +the Ryan Dahl's C Version: `http-parser` available here: + + http://github.com/ry/http-parser + +It parses both requests and responses. The parser is designed to be used +in performance HTTP applications. + +Features: + + * No dependencies (probably won't be able to keep it up) + * Handles persistent streams (keep-alive). + * Decodes chunked encoding. + * Upgrade support + +The parser extracts the following information from HTTP messages: + + * Header fields and values + * Content-Length + * Request method + * Response status code + * Transfer-Encoding + * HTTP version + * Request URL + * Message body + +Building +-------- + +use `ant compile|test|jar` + +Usage +----- + + TODO: in the present form, usage of the Java version of the parser + shouldn't be too difficult to figure out for someone familiar with the + C version. + + More documentation will follow shortly, in case you're looking for an + easy to use http library, this lib is probably not what you are + looking for anyway ... + + All text after this paragraph (and most of the text above it) are from + the original C version of the README and are currently only here for + reference. In case you encounter any difficulties, find bugs, need + help or have suggestions, feel free to contact me at + (tim.becker@kuriositaet.de). + + +One `http_parser` object is used per TCP connection. Initialize the struct +using `http_parser_init()` and set the callbacks. That might look something +like this for a request parser: + + http_parser_settings settings; + settings.on_path = my_path_callback; + settings.on_header_field = my_header_field_callback; + /* ... */ + + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); + parser->data = my_socket; + +When data is received on the socket execute the parser and check for errors. + + size_t len = 80*1024, nparsed; + char buf[len]; + ssize_t recved; + + recved = recv(fd, buf, len, 0); + + if (recved < 0) { + /* Handle error. */ + } + + /* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been recieved. + */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + if (parser->upgrade) { + /* handle new protocol */ + } else if (nparsed != recved) { + /* Handle error. Usually just close the connection. */ + } + +HTTP needs to know where the end of the stream is. For example, sometimes +servers send responses without Content-Length and expect the client to +consume input (for the body) until EOF. To tell http_parser about EOF, give +`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors +can still be encountered during an EOF, so one must still be prepared +to receive them. + +Scalar valued message information such as `status_code`, `method`, and the +HTTP version are stored in the parser structure. This data is only +temporally stored in `http_parser` and gets reset on each new message. If +this information is needed later, copy it out of the structure during the +`headers_complete` callback. + +The parser decodes the transfer-encoding for both requests and responses +transparently. That is, a chunked encoding is decoded before being sent to +the on_body callback. + + +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the Web Socket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more +information the Web Socket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body. Issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() will stop parsing at the end of the headers and return. + +The user is expected to check if `parser->upgrade` has been set to 1 after +`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied +offset by the return value of `http_parser_execute()`. + + +Callbacks +--------- + +During the `http_parser_execute()` call, the callbacks set in +`http_parser_settings` will be executed. The parser maintains state and +never looks behind, so buffering the data is not necessary. If you need to +save certain data for later usage, you can do that from the callbacks. + +There are two types of callbacks: + +* notification `typedef int (*http_cb) (http_parser*);` + Callbacks: on_message_begin, on_headers_complete, on_message_complete. +* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` + Callbacks: (requests only) on_uri, + (common) on_header_field, on_header_value, on_body; + +Callbacks must return 0 on success. Returning a non-zero value indicates +error to the parser, making it exit immediately. + +In case you parse HTTP message in chunks (i.e. `read()` request line +from socket, parse, read half headers, parse, etc) your data callbacks +may be called more than once. Http-parser guarantees that data pointer is only +valid for the lifetime of callback. You can also `read()` into a heap allocated +buffer to avoid copying memory around if this fits your application. + +Reading headers may be a tricky task if you read/parse headers partially. +Basically, you need to remember whether last header callback was field or value +and apply following logic: + + (on_header_field and on_header_value shortened to on_h_*) + ------------------------ ------------ -------------------------------------------- + | State (prev. callback) | Callback | Description/action | + ------------------------ ------------ -------------------------------------------- + | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | + | | | into it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_field | New header started. | + | | | Copy current name,value buffers to headers | + | | | list and allocate new buffer for new name | + ------------------------ ------------ -------------------------------------------- + | field | on_h_field | Previous name continues. Reallocate name | + | | | buffer and append callback data to it | + ------------------------ ------------ -------------------------------------------- + | field | on_h_value | Value for current header started. Allocate | + | | | new buffer and copy callback data to it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_value | Value continues. Reallocate value buffer | + | | | and append callback data to it | + ------------------------ ------------ -------------------------------------------- diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/TODO b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/TODO new file mode 100644 index 0000000..eb46a08 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/TODO @@ -0,0 +1,28 @@ +decide how to handle errs per default: + - ry: "set state to dead", return `read` + - current: call on_error w/ details, if no on_error handler set, + throw Exception, else call on_error and behave like orig... + +some tests from test.c left to port + (scan ...) +documentation + +hi level callback interface +eventloop +state() as a function (?) + - perhaps, the idea being to be able to log/debug better... +more tests + - in particular, port available c tests +impl bits of servlet api. + +DONE + +Sun Jul 18 12:19:18 CEST 2010 + +error handling + - consider callback based error handling and the current highlevel + "nice" logging moved to high level http impl. + - use Exceptions "ProtocolException"? + +better testing + - no junit to avoid dependencies diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/build.xml b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/build.xml new file mode 100755 index 0000000..d2c6af4 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/build.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c new file mode 100644 index 0000000..e961ae8 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c @@ -0,0 +1,2175 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (parser->state) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (parser->state == s_header_field) + header_field_mark = data; + if (parser->state == s_header_value) + header_value_mark = data; + switch (parser->state) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(parser->state)) { + ++parser->nread; + /* Buffer overflow attack */ + if (parser->nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + reexecute_byte: + switch (parser->state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (ch == CR || ch == LF) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + parser->state = s_res_or_resp_H; + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + parser->state = s_start_req; + goto reexecute_byte; + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + parser->state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + parser->state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + parser->state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '0' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + parser->state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + parser->state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + parser->state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + parser->state = s_res_status; + break; + case CR: + parser->state = s_res_line_almost_done; + break; + case LF: + parser->state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + parser->state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + parser->state = s_header_field_start; + CALLBACK_NOTIFY(status_complete); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + parser->state = s_req_method; + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (parser->index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (parser->index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (parser->index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (parser->index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (parser->index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (parser->method == HTTP_SUBSCRIBE) { + if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_SEARCH; + } else { + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + goto error; + } + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') parser->method = HTTP_PURGE; + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + parser->state = s_req_server_start; + } + + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + parser->state = s_req_http_start; + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + parser->state = (ch == CR) ? + s_req_line_almost_done : + s_header_field_start; + CALLBACK_DATA(url); + break; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + parser->state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + parser->state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + parser->state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == CR) { + parser->state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + parser->state = s_headers_almost_done; + goto reexecute_byte; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + parser->state = s_header_field; + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + parser->state = s_header_value_start; + CALLBACK_DATA(header_field); + break; + } + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_field); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_start: + { + if (ch == ' ' || ch == '\t') break; + + MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + + if (ch == CR) { + parser->header_state = h_general; + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_value); + break; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else { + parser->header_state = h_general; + } + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_almost_done; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + parser->header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + parser->header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CLOSE)-2) { + parser->header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + parser->state = s_header_value; + parser->header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + { + STRICT_CHECK(ch != LF); + + parser->state = s_header_value_lws; + + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + parser->state = s_header_value_start; + else + { + parser->state = s_header_field_start; + goto reexecute_byte; + } + break; + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + } + + parser->state = s_headers_done; + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return p - data; + } + + goto reexecute_byte; + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + parser->state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + parser->state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + parser->state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + goto reexecute_byte; + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + parser->state = s_chunk_size; + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + parser->state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + parser->state = s_header_field_start; + } else { + parser->state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + parser->state = s_chunk_data_done; + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + parser->state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + uf = old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp new file mode 100644 index 0000000..c6eada7 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp @@ -0,0 +1,79 @@ +# This file is used with the GYP meta build system. +# http://code.google.com/p/gyp/ +# To build try this: +# svn co http://gyp.googlecode.com/svn/trunk gyp +# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp +# ./out/Debug/test +{ + 'target_defaults': { + 'default_configuration': 'Debug', + 'configurations': { + # TODO: hoist these out and put them somewhere common, because + # RuntimeLibrary MUST MATCH across the entire project + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 1, # static debug + }, + }, + }, + 'Release': { + 'defines': [ 'NDEBUG' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 0, # static release + }, + }, + } + }, + 'msvs_settings': { + 'VCCLCompilerTool': { + }, + 'VCLibrarianTool': { + }, + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + }, + }, + 'conditions': [ + ['OS == "win"', { + 'defines': [ + 'WIN32' + ], + }] + ], + }, + + 'targets': [ + { + 'target_name': 'http_parser', + 'type': 'static_library', + 'include_dirs': [ '.' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + }, + 'defines': [ 'HTTP_PARSER_STRICT=0' ], + 'sources': [ './http_parser.c', ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. http_parser.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + }, + }] + ], + }, + + { + 'target_name': 'test', + 'type': 'executable', + 'dependencies': [ 'http_parser' ], + 'sources': [ 'test.c' ] + } + ] +} + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h new file mode 100644 index 0000000..2fff4bd --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h @@ -0,0 +1,304 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +#include +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed */ +#define HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* webdav */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + /* subversion */ \ + XX(16, REPORT, REPORT) \ + XX(17, MKACTIVITY, MKACTIVITY) \ + XX(18, CHECKOUT, CHECKOUT) \ + XX(19, MERGE, MERGE) \ + /* upnp */ \ + XX(20, MSEARCH, M-SEARCH) \ + XX(21, NOTIFY, NOTIFY) \ + XX(22, SUBSCRIBE, SUBSCRIBE) \ + XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(24, PATCH, PATCH) \ + XX(25, PURGE, PURGE) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_status_complete, "the on_status_complete callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned char type : 2; /* enum http_parser_type */ + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; /* enum state from http_parser.c */ + unsigned char header_state; /* enum header_state from http_parser.c */ + unsigned char index; /* index into current matcher */ + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + unsigned char http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned char upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_cb on_status_complete; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml new file mode 100644 index 0000000..741121a --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java new file mode 100644 index 0000000..774179f --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java @@ -0,0 +1,41 @@ +package http_parser; + +public class FieldData { + public int off; + public int len; + + public FieldData(){} + + public FieldData(int off, int len){ + this.off = off; + this.len = len; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FieldData fieldData = (FieldData) o; + + if (len != fieldData.len) return false; + if (off != fieldData.off) return false; + + return true; + } + + @Override + public int hashCode() { + int result = off; + result = 31 * result + len; + return result; + } + + @Override + public String toString() { + return "FieldData{" + + "off=" + off + + ", len=" + len + + '}'; + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java new file mode 100644 index 0000000..5380b0f --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java @@ -0,0 +1,8 @@ +package http_parser; + +public abstract class HTTPCallback implements http_parser.lolevel.HTTPCallback{ + public int cb (http_parser.lolevel.HTTPParser parser) { + return this.cb((HTTPParser)parser); + } + public abstract int cb (HTTPParser parser); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java new file mode 100644 index 0000000..bfe576f --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java @@ -0,0 +1,34 @@ +package http_parser; + +import java.nio.ByteBuffer; + +public abstract class HTTPDataCallback implements http_parser.lolevel.HTTPDataCallback{ + /* + Very raw and extremly foolhardy! DANGER! + The whole Buffer concept is difficult enough to grasp as it is, + we pass in a buffer with an arbitrary position. + + The interesting data is located at position pos and is len + bytes long. + + The contract of this callback is that the buffer is + returned in the state that it was passed in, so implementing + this require good citizenship, you'll need to remember the current + position, change the position to get at the data you're interested + in and then set the position back to how you found it... + + Therefore: there is an abstract implementation that implements + cb as described above, and provides a new callback + with signature @see cb(byte[], int, int) + */ + public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { + byte [] by = new byte[len]; + int saved = buf.position(); + buf.position(pos); + buf.get(by); + buf.position(saved); + return cb((HTTPParser)p, by, 0, len); + } + + public abstract int cb(HTTPParser p, byte[] by, int pos, int len); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java new file mode 100644 index 0000000..a74206e --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java @@ -0,0 +1,12 @@ +package http_parser; + + +import java.nio.ByteBuffer; + +public abstract class HTTPErrorCallback implements http_parser.lolevel.HTTPErrorCallback{ + public void cb (http_parser.lolevel.HTTPParser parser, String mes, ByteBuffer buf, int initial_position) { + this.cb((HTTPParser)parser, Util.error(mes, buf, initial_position)); + } + + public abstract void cb(HTTPParser parser, String error); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java new file mode 100644 index 0000000..9ccaf14 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java @@ -0,0 +1,9 @@ +package http_parser; + +@SuppressWarnings("serial") +public class HTTPException extends RuntimeException { + +public HTTPException(String mes) { + super(mes); + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java new file mode 100644 index 0000000..7c080c1 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java @@ -0,0 +1,107 @@ +package http_parser; + +import java.nio.charset.Charset; + +public enum HTTPMethod { + HTTP_DELETE("DELETE")// = 0 + , HTTP_GET("GET") + , HTTP_HEAD("HEAD") + , HTTP_POST("POST") + , HTTP_PUT("PUT") + , HTTP_PATCH("PATCH") + /* pathological */ + , HTTP_CONNECT("CONNECT") + , HTTP_OPTIONS("OPTIONS") + , HTTP_TRACE("TRACE") + /* webdav */ + , HTTP_COPY("COPY") + , HTTP_LOCK("LOCK") + , HTTP_MKCOL("MKCOL") + , HTTP_MOVE("MOVE") + , HTTP_PROPFIND("PROPFIND") + , HTTP_PROPPATCH("PROPPATCH") + , HTTP_UNLOCK("UNLOCK") + , HTTP_REPORT("REPORT") + , HTTP_MKACTIVITY("MKACTIVITY") + , HTTP_CHECKOUT("CHECKOUT") + , HTTP_MERGE("MERGE") + , HTTP_MSEARCH("M-SEARCH") + , HTTP_NOTIFY("NOTIFY") + , HTTP_SUBSCRIBE("SUBSCRIBE") + , HTTP_UNSUBSCRIBE("UNSUBSCRIBE") + , HTTP_PURGE("PURGE") + ; + + private static Charset ASCII; + static { + ASCII = Charset.forName("US-ASCII");; + } + public byte[] bytes; + + HTTPMethod(String name) { + // good grief, Charlie Brown, the following is necessary because + // java is retarded: + // illegal reference to static field from initializer + // this.bytes = name.getBytes(ASCII); + // yet it's not illegal to reference static fields from + // methods called from initializer. + init(name); + } + public static HTTPMethod parse(String s) { + if ("HTTP_DELETE".equalsIgnoreCase(s)) {return HTTP_DELETE;} + else if ("DELETE".equalsIgnoreCase(s)) {return HTTP_DELETE;} + else if ("HTTP_GET".equalsIgnoreCase(s)) {return HTTP_GET;} + else if ("GET".equalsIgnoreCase(s)) {return HTTP_GET;} + else if ("HTTP_HEAD".equalsIgnoreCase(s)) {return HTTP_HEAD;} + else if ("HEAD".equalsIgnoreCase(s)) {return HTTP_HEAD;} + else if ("HTTP_POST".equalsIgnoreCase(s)) {return HTTP_POST;} + else if ("POST".equalsIgnoreCase(s)) {return HTTP_POST;} + else if ("HTTP_PUT".equalsIgnoreCase(s)) {return HTTP_PUT;} + else if ("PUT".equalsIgnoreCase(s)) {return HTTP_PUT;} + else if ("HTTP_PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;} + else if ("PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;} + else if ("HTTP_CONNECT".equalsIgnoreCase(s)) {return HTTP_CONNECT;} + else if ("CONNECT".equalsIgnoreCase(s)) {return HTTP_CONNECT;} + else if ("HTTP_OPTIONS".equalsIgnoreCase(s)) {return HTTP_OPTIONS;} + else if ("OPTIONS".equalsIgnoreCase(s)) {return HTTP_OPTIONS;} + else if ("HTTP_TRACE".equalsIgnoreCase(s)) {return HTTP_TRACE;} + else if ("TRACE".equalsIgnoreCase(s)) {return HTTP_TRACE;} + else if ("HTTP_COPY".equalsIgnoreCase(s)) {return HTTP_COPY;} + else if ("COPY".equalsIgnoreCase(s)) {return HTTP_COPY;} + else if ("HTTP_LOCK".equalsIgnoreCase(s)) {return HTTP_LOCK;} + else if ("LOCK".equalsIgnoreCase(s)) {return HTTP_LOCK;} + else if ("HTTP_MKCOL".equalsIgnoreCase(s)) {return HTTP_MKCOL;} + else if ("MKCOL".equalsIgnoreCase(s)) {return HTTP_MKCOL;} + else if ("HTTP_MOVE".equalsIgnoreCase(s)) {return HTTP_MOVE;} + else if ("MOVE".equalsIgnoreCase(s)) {return HTTP_MOVE;} + else if ("HTTP_PROPFIND".equalsIgnoreCase(s)){return HTTP_PROPFIND;} + else if ("PROPFIND".equalsIgnoreCase(s)) {return HTTP_PROPFIND;} + else if ("HTTP_PROPPATCH".equalsIgnoreCase(s)){return HTTP_PROPPATCH;} + else if ("PROPPATCH".equalsIgnoreCase(s)) {return HTTP_PROPPATCH;} + else if ("HTTP_UNLOCK".equalsIgnoreCase(s)) {return HTTP_UNLOCK;} + else if ("UNLOCK".equalsIgnoreCase(s)) {return HTTP_UNLOCK;} + else if ("HTTP_REPORT".equalsIgnoreCase(s)) {return HTTP_REPORT;} + else if ("REPORT".equalsIgnoreCase(s)){return HTTP_REPORT;} + else if ("HTTP_MKACTIVITY".equalsIgnoreCase(s)) {return HTTP_MKACTIVITY;} + else if ("MKACTIVITY".equalsIgnoreCase(s)){return HTTP_MKACTIVITY;} + else if ("HTTP_CHECKOUT".equalsIgnoreCase(s)) {return HTTP_CHECKOUT;} + else if ("CHECKOUT".equalsIgnoreCase(s)){return HTTP_CHECKOUT;} + else if ("HTTP_MERGE".equalsIgnoreCase(s)) {return HTTP_MERGE;} + else if ("MERGE".equalsIgnoreCase(s)){return HTTP_MERGE;} + else if ("HTTP_MSEARCH".equalsIgnoreCase(s)) {return HTTP_MSEARCH;} + else if ("M-SEARCH".equalsIgnoreCase(s)) {return HTTP_MSEARCH;} + else if ("HTTP_NOTIFY".equalsIgnoreCase(s)) {return HTTP_NOTIFY;} + else if ("NOTIFY".equalsIgnoreCase(s)) {return HTTP_NOTIFY;} + else if ("HTTP_SUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_SUBSCRIBE;} + else if ("SUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_SUBSCRIBE;} + else if ("HTTP_UNSUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_UNSUBSCRIBE;} + else if ("UNSUBSCRIBE".equalsIgnoreCase(s)) {return HTTP_UNSUBSCRIBE;} + else if ("PATCH".equalsIgnoreCase(s)) {return HTTP_PATCH;} + else if ("PURGE".equalsIgnoreCase(s)) {return HTTP_PURGE;} + else {return null;} + } + void init (String name) { + ASCII = null == ASCII ? Charset.forName("US-ASCII") : ASCII; + this.bytes = name.getBytes(ASCII); + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java new file mode 100644 index 0000000..7ab4fb4 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java @@ -0,0 +1,36 @@ +package http_parser; + +import java.nio.ByteBuffer; + +public class HTTPParser extends http_parser.lolevel.HTTPParser { + + public HTTPParser() { super(); } + public HTTPParser(ParserType type) { super(type); } + + public int getMajor() { + return super.http_major; + } + + public int getMinor() { + return super.http_minor; + } + + public int getStatusCode() { + return super.status_code; + } + + public HTTPMethod getHTTPMethod() { + return super.method; + } + + public boolean getUpgrade() { + return super.upgrade; + } + + public boolean shouldKeepAlive() { + return super.http_should_keep_alive(); + } + public void execute(ParserSettings settings, ByteBuffer data) { + this.execute(settings.getLoLevelSettings(), data); + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java new file mode 100644 index 0000000..d371634 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java @@ -0,0 +1,76 @@ +package http_parser; + +import http_parser.lolevel.*; +import http_parser.lolevel.HTTPParser; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + */ +public class HTTPParserUrl { + + public int field_set; + public int port; + + public FieldData[] field_data = new FieldData[]{ + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0) + }; //UF_MAX + + public HTTPParserUrl(){} + + public HTTPParserUrl(int field_set, int port, FieldData[] field_data){ + this.field_set = field_set; + this.port = port; + this.field_data = field_data; + } + + public String getFieldValue(HTTPParser.UrlFields field, ByteBuffer data) throws UnsupportedEncodingException { + FieldData fd = this.field_data[field.getIndex()]; + if(fd.off == 0 & fd.len == 0) return ""; + byte[] dst = new byte[fd.len]; + int current_pos = data.position(); + data.position(fd.off); + data.get(dst,0,fd.len); + data.position(current_pos); + String v = new String(dst, "UTF8"); + return v; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HTTPParserUrl that = (HTTPParserUrl) o; + + if (field_set != that.field_set) return false; + if (port != that.port) return false; + if (!Arrays.equals(field_data, that.field_data)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = field_set; + result = 31 * result + port; + result = 31 * result + Arrays.hashCode(field_data); + return result; + } + + @Override + public String toString() { + return "HTTPParserUrl{" + + "field_set=" + field_set + + ", port=" + port + + ", field_data=" + (field_data == null ? null : Arrays.asList(field_data)) + + '}'; + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java new file mode 100644 index 0000000..9a5e6e9 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java @@ -0,0 +1,256 @@ +package http_parser; + + + +import primitive.collection.ByteList; + +public class ParserSettings extends http_parser.lolevel.ParserSettings { + + public HTTPCallback on_message_begin; + public HTTPDataCallback on_path; + public HTTPDataCallback on_query_string; + public HTTPDataCallback on_url; + public HTTPDataCallback on_fragment; + public HTTPCallback on_status_complete; + public HTTPDataCallback on_header_field; + public HTTPDataCallback on_header_value; + + public HTTPCallback on_headers_complete; + public HTTPDataCallback on_body; + public HTTPCallback on_message_complete; + + public HTTPErrorCallback on_error; + + private HTTPCallback _on_message_begin; + private HTTPDataCallback _on_path; + private HTTPDataCallback _on_query_string; + private HTTPDataCallback _on_url; + private HTTPDataCallback _on_fragment; + private HTTPCallback _on_status_complete; + private HTTPDataCallback _on_header_field; + private HTTPDataCallback _on_header_value; + private HTTPCallback _on_headers_complete; + private HTTPDataCallback _on_body; + private HTTPCallback _on_message_complete; + private HTTPErrorCallback _on_error; + + private http_parser.lolevel.ParserSettings settings; + + protected ByteList field = new ByteList(); + protected ByteList value = new ByteList(); + protected ByteList body = new ByteList(); + + public ParserSettings() { + this.settings = new http_parser.lolevel.ParserSettings(); + createMirrorCallbacks(); + attachCallbacks(); + } + + protected http_parser.lolevel.ParserSettings getLoLevelSettings() { + return this.settings; + } + + private void createMirrorCallbacks() { + this._on_message_begin = new HTTPCallback() { + public int cb(HTTPParser p) { + if (null != ParserSettings.this.on_message_begin) { + return ParserSettings.this.on_message_begin.cb(p); + } + return 0; + } + }; + this._on_path = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + if (null != ParserSettings.this.on_path) { + return ParserSettings.this.on_path.cb(p, by, pos, len); + } + return 0; + } + }; + this._on_query_string = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + if (null != ParserSettings.this.on_query_string) { + return ParserSettings.this.on_query_string.cb(p, by, pos, len); + } + return 0; + } + }; + this._on_url = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + if (null != ParserSettings.this.on_url) { + return ParserSettings.this.on_url.cb(p, by, pos, len); + } + return 0; + } + }; + this._on_fragment = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + if (null != ParserSettings.this.on_fragment) { + return ParserSettings.this.on_fragment.cb(p, by, pos, len); + } + return 0; + } + }; + this._on_status_complete = new HTTPCallback() { + @Override + public int cb(HTTPParser p) { + if (null != ParserSettings.this.on_status_complete) { + return ParserSettings.this.on_status_complete.cb(p); + } + return 0; + } + }; + this._on_error = new HTTPErrorCallback() { + @Override + public void cb(HTTPParser parser, String error) { + if (null != ParserSettings.this.on_error) { + ParserSettings.this.on_error.cb(parser, error); + } else { + throw new HTTPException(error); + } + + } + }; + + + +// (on_header_field and on_header_value shortened to on_h_*) +// ------------------------ ------------ -------------------------------------------- +// | State (prev. callback) | Callback | Description/action | +// ------------------------ ------------ -------------------------------------------- +// | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | +// | | | into it | +// ------------------------ ------------ -------------------------------------------- +// | value | on_h_field | New header started. | +// | | | Copy current name,value buffers to headers | +// | | | list and allocate new buffer for new name | +// ------------------------ ------------ -------------------------------------------- +// | field | on_h_field | Previous name continues. Reallocate name | +// | | | buffer and append callback data to it | +// ------------------------ ------------ -------------------------------------------- +// | field | on_h_value | Value for current header started. Allocate | +// | | | new buffer and copy callback data to it | +// ------------------------ ------------ -------------------------------------------- +// | value | on_h_value | Value continues. Reallocate value buffer | +// | | | and append callback data to it | +// ------------------------ ------------ -------------------------------------------- + this._on_header_field = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + // previous value complete, call on_value with full value, reset value. + if (0 != ParserSettings.this.value.size()) { + // check we're even interested... + if (null != ParserSettings.this.on_header_value) { + byte [] valueArr = ParserSettings.this.value.toArray(); + int ret = ParserSettings.this.on_header_value.cb(p, valueArr, 0, valueArr.length); + if (0 != ret) { + return ret; + } + ParserSettings.this.value.clear(); + } + } + + if (null == ParserSettings.this.on_header_field) { + return 0; + } + + ParserSettings.this.field.addAll(by); + return 0; + } + }; + this._on_header_value = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + + // previous field complete, call on_field with full field value, reset field. + if (0 != ParserSettings.this.field.size()) { + // check we're even interested... + if (null != ParserSettings.this.on_header_field) { + byte [] fieldArr = ParserSettings.this.field.toArray(); + int ret = ParserSettings.this.on_header_field.cb(p, fieldArr, 0, fieldArr.length); + if (0 != ret) { + return ret; + } + ParserSettings.this.field.clear(); + } + } + + if (null == ParserSettings.this.on_header_value) { + return 0; + } + ParserSettings.this.value.addAll(by); + return 0; + } + }; + this._on_headers_complete = new HTTPCallback() { + @Override + public int cb(HTTPParser parser) { + // is there an uncompleted value ... ? + if (0 != ParserSettings.this.value.size()) { + // check we're even interested... + if (null != ParserSettings.this.on_header_value) { + byte [] valueArr = ParserSettings.this.value.toArray(); + int ret = ParserSettings.this.on_header_value.cb(parser, valueArr, 0, valueArr.length); + if (0 != ret) { + return ret; + } + ParserSettings.this.value.clear(); + } + } + if (null != ParserSettings.this.on_headers_complete) { + return ParserSettings.this.on_headers_complete.cb(parser); + } + return 0; + } + + }; + this._on_body = new HTTPDataCallback() { + @Override + public int cb(HTTPParser p, byte[] by, int pos, int len) { + if (null != ParserSettings.this.on_body) { + ParserSettings.this.body.addAll(by, pos, len); + } + return 0; + } + }; + + this._on_message_complete = new HTTPCallback() { + @Override + public int cb(HTTPParser parser) { + if (null != ParserSettings.this.on_body) { + byte [] body = ParserSettings.this.body.toArray(); + int ret = ParserSettings.this.on_body.cb(parser, body, 0, body.length); + if (0!=ret) { + return ret; + } + ParserSettings.this.body.clear(); + } + if (null != ParserSettings.this.on_message_complete) { + return ParserSettings.this.on_message_complete.cb(parser); + } + return 0; + } + }; + + } + + private void attachCallbacks() { + // these are certainly set, because we mirror them ... + this.settings.on_message_begin = this._on_message_begin; + this.settings.on_path = this._on_path; + this.settings.on_query_string = this._on_query_string; + this.settings.on_url = this._on_url; + this.settings.on_fragment = this._on_fragment; + this.settings.on_status_complete = this._on_status_complete; + this.settings.on_header_field = this._on_header_field; + this.settings.on_header_value = this._on_header_value; + this.settings.on_headers_complete = this._on_headers_complete; + this.settings.on_body = this._on_body; + this.settings.on_message_complete = this._on_message_complete; + this.settings.on_error = this._on_error; + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java new file mode 100644 index 0000000..a51f5b4 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java @@ -0,0 +1,13 @@ +package http_parser; + +public enum ParserType { +HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH; + + public static ParserType parse(String s) { + if ("HTTP_REQUEST".equalsIgnoreCase(s)) { return HTTP_REQUEST; } + else if ("HTTP_RESPONSE".equalsIgnoreCase(s)) { return HTTP_RESPONSE; } + else if ("HTTP_BOTH".equalsIgnoreCase(s)) { return HTTP_BOTH; } + else { return null; } + } +} + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java new file mode 100644 index 0000000..575003a --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java @@ -0,0 +1,111 @@ +package http_parser; + +import java.nio.ByteBuffer; + +public class Util { +// public static String toString(http_parser.lolevel.HTTPParser p) { +// StringBuilder builder = new StringBuilder(); +// +// // the stuff up to the break is ephermeral and only meaningful +// // while the parser is parsing. In general, this method is +// // probably only useful during debugging. +// +// builder.append("state :"); builder.append(p.state); builder.append("\n"); +// builder.append("header_state :"); builder.append(p.header_state); builder.append("\n"); +// builder.append("strict :"); builder.append(p.strict); builder.append("\n"); +// builder.append("index :"); builder.append(p.index); builder.append("\n"); +// builder.append("flags :"); builder.append(p.flags); builder.append("\n"); +// builder.append("nread :"); builder.append(p.nread); builder.append("\n"); +// builder.append("content_length :"); builder.append(p.content_length); builder.append("\n"); +// +// +// builder.append("type :"); builder.append(p.type); builder.append("\n"); +// builder.append("http_major :"); builder.append(p.http_major); builder.append("\n"); +// builder.append("http_minor :"); builder.append(p.http_minor); builder.append("\n"); +// builder.append("status_code :"); builder.append(p.status_code); builder.append("\n"); +// builder.append("method :"); builder.append(p.method); builder.append("\n"); +// builder.append("upgrade :"); builder.append(p.upgrade); builder.append("\n"); +// +// return builder.toString(); +// +// } + + public static String error (String mes, ByteBuffer b, int beginning) { + // the error message should look like this: + // + // Bla expected something, but it's not there (mes) + // GEt / HTTP 1_1 + // ............^. + // + // |----------------- 72 -------------------------| + + // This is ridiculously complicated and probably riddled with + // off-by-one errors, should be moved into high level interface. + // TODO. + + // also: need to keep track of the initial buffer position in + // execute so that we don't screw up any `mark()` that may have + // been set outside of our control to be nice. + + final int mes_width = 72; + int p = b.position(); // error position + int end = b.limit(); // this is the end + int m = end - beginning; // max mes length + + StringBuilder builder = new StringBuilder(); + int p_adj = p; + + byte [] orig = new byte[0]; + if (m <= mes_width) { + orig = new byte[m]; + b.position(beginning); + b.get(orig, 0, m); + p_adj = p-beginning; + + + } else { + // we'll need to trim bit off the beginning and/or end + orig = new byte[mes_width]; + // three possibilities: + // a.) plenty of stuff around p + // b.) plenty of stuff in front of p + // c.) plenty of stuff behind p + // CAN'T be not enough stuff aorund p in total, because + // m>meswidth (see if to this else) + + int before = p-beginning; + int after = end - p; + if ( (before > mes_width/2) && (after > mes_width/2)) { + // plenty of stuff in front of and behind error + p_adj = mes_width/2; + b.position(p - mes_width/2); + b.get(orig, 0, mes_width); + } else if (before <= mes_width/2) { + // take all of the begining. + b.position(beginning); + // and as much of the rest as possible + + b.get(orig, 0, mes_width); + + } else { + // plenty of stuff before + before = end-mes_width; + b.position(before); + p_adj = p - before; + b.get(orig, 0, mes_width); + } + } + + builder.append(new String(orig)); + builder.append("\n"); + for (int i = 0; i!= p_adj; ++i) { + builder.append("."); + } + builder.append("^"); + + + b.position(p); // restore position + return builder.toString(); + + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java new file mode 100644 index 0000000..95c29b3 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java @@ -0,0 +1,5 @@ +package http_parser.lolevel; + +public interface HTTPCallback { + public int cb (HTTPParser parser); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java new file mode 100644 index 0000000..6cad156 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java @@ -0,0 +1,25 @@ +package http_parser.lolevel; + +import java.nio.ByteBuffer; + +public interface HTTPDataCallback { + /* + very raw and extremly foolhardy! DANGER! + The whole Buffer concept is difficult enough to grasp as it is, + we pass in a buffer with an arbitrary position. + + The interesting data is located at position pos and is len + bytes long. + + The contract of this callback is that the buffer is + returned in the state that it was passed in, so implementing + this require good citizenship, you'll need to remember the current + position, change the position to get at the data you're interested + in and then set the position back to how you found it... + + //TODO: there should be an abstract implementation that implements + cb as described above, marks it final an provides a new callback + with signature cb(byte[], int, int) + */ + public int cb(HTTPParser p, ByteBuffer buf, int pos, int len); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java new file mode 100644 index 0000000..d38d9d4 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java @@ -0,0 +1,7 @@ +package http_parser.lolevel; + +import java.nio.ByteBuffer; + +public interface HTTPErrorCallback { + public void cb (HTTPParser parser, String mes, ByteBuffer buf, int initial_position); +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java new file mode 100644 index 0000000..42022ec --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java @@ -0,0 +1,2161 @@ +package http_parser.lolevel; + +import java.nio.ByteBuffer; +import http_parser.HTTPException; +import http_parser.HTTPMethod; +import http_parser.HTTPParserUrl; +import http_parser.ParserType; +import static http_parser.lolevel.HTTPParser.C.*; +import static http_parser.lolevel.HTTPParser.State.*; + +public class HTTPParser { + /* lots of unsigned chars here, not sure what + to about them, `bytes` in java suck... */ + + ParserType type; + State state; + HState header_state; + boolean strict; + + int index; + int flags; // TODO + + int nread; + long content_length; + + int p_start; // updated each call to execute to indicate where the buffer was before we began calling it. + + /** READ-ONLY **/ + public int http_major; + public int http_minor; + public int status_code; /* responses only */ + public HTTPMethod method; /* requests only */ + + /* true = Upgrade header was present and the parser has exited because of that. + * false = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + public boolean upgrade; + + /** PUBLIC **/ + // TODO : this is used in c to maintain application state. + // is this even necessary? we have state in java ? + // consider + // Object data; /* A pointer to get hook to the "connection" or "socket" object */ + + + /* + * technically we could combine all of these (except for url_mark) into one + * variable, saving stack space, but it seems more clear to have them + * separated. + */ + int header_field_mark = -1; + int header_value_mark = -1; + int url_mark = -1; + int body_mark = -1; + + /** + * Construct a Parser for ParserType.HTTP_BOTH, meaning it + * determines whether it's parsing a request or a response. + */ + public HTTPParser() { + this(ParserType.HTTP_BOTH); + } + + /** + * Construct a Parser and initialise it to parse either + * requests or responses. + */ + public HTTPParser(ParserType type) { + this.type = type; + switch(type) { + case HTTP_REQUEST: + this.state = State.start_req; + break; + case HTTP_RESPONSE: + this.state = State.start_res; + break; + case HTTP_BOTH: + this.state = State.start_req_or_res; + break; + default: + throw new HTTPException("can't happen, invalid ParserType enum"); + } + } + + /* + * Utility to facilitate System.out.println style debugging (the way god intended) + */ + static void p(Object o) {System.out.println(o);} + + /** Comment from C version follows + * + * Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ + public State parse_url_char(byte ch) { + + int chi = ch & 0xff; // utility, ch without signedness for table lookups. + + if(SPACE == ch){ + throw new HTTPException("space as url char"); + } + + switch(state) { + case req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + if(SLASH == ch || STAR == ch){ + return req_path; + } + if(isAtoZ(ch)){ + return req_schema; + } + break; + case req_schema: + if(isAtoZ(ch)){ + return req_schema; + } + if(COLON == ch){ + return req_schema_slash; + } + break; + case req_schema_slash: + if(SLASH == ch){ + return req_schema_slash_slash; + } + break; + case req_schema_slash_slash: + if(SLASH == ch){ + return req_host_start; + } + break; + case req_host_start: + if (ch == (byte)'[') { + return req_host_v6_start; + } + if (isHostChar(ch)) { + return req_host; + } + break; + + case req_host: + if (isHostChar(ch)) { + return req_host; + } + + /* FALLTHROUGH */ + case req_host_v6_end: + switch (ch) { + case ':': + return req_port_start; + case '/': + return req_path; + case '?': + return req_query_string_start; + } + break; + + case req_host_v6: + if (ch == ']') { + return req_host_v6_end; + } + + /* FALLTHROUGH */ + case req_host_v6_start: + if (isHex(ch) || ch == ':') { + return req_host_v6; + } + break; + + case req_port: + switch (ch) { + case '/': + return req_path; + case '?': + return req_query_string_start; + } + + /* FALLTHROUGH */ + case req_port_start: + if (isDigit(ch)) { + return req_port; + } + break; + + case req_path: + if (isNormalUrlChar(chi)) { + return req_path; + } + switch (ch) { + case '?': + return req_query_string_start; + case '#': + return req_fragment_start; + } + + break; + + case req_query_string_start: + case req_query_string: + if (isNormalUrlChar(chi)) { + return req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return req_query_string; + + case '#': + return req_fragment_start; + } + + break; + + case req_fragment_start: + if (isNormalUrlChar(chi)) { + return req_fragment; + } + switch (ch) { + case '?': + return req_fragment; + + case '#': + return req_fragment_start; + } + break; + + case req_fragment: + if (isNormalUrlChar(ch)) { + return req_fragment; + } + + switch (ch) { + case '?': + case '#': + return req_fragment; + } + + break; + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return dead; + } + + /** Execute the parser with the currently available data contained in + * the buffer. The buffers position() and limit() need to be set + * correctly (obviously) and a will be updated approriately when the + * method returns to reflect the consumed data. + */ + public int execute(ParserSettings settings, ByteBuffer data) { + + int p = data.position(); + this.p_start = p; // this is used for pretty printing errors. + // and returning the amount of processed bytes. + + + // In case the headers don't provide information about the content + // length, `execute` needs to be called with an empty buffer to + // indicate that all the data has been send be the client/server, + // else there is no way of knowing the message is complete. + int len = (data.limit() - data.position()); + if (0 == len) { +// if (State.body_identity_eof == state) { +// settings.call_on_message_complete(this); +// } + switch (state) { + case body_identity_eof: + settings.call_on_message_complete(this); + return data.position() - this.p_start; + + case dead: + case start_req_or_res: + case start_res: + case start_req: + return data.position() - this.p_start; + + default: + // should we really consider this an error!? + throw new HTTPException("empty bytes! "+state); // error + } + } + + + // in case the _previous_ call to the parser only has data to get to + // the middle of certain fields, we need to update marks to point at + // the beginning of the current buffer. + switch (state) { + case header_field: + header_field_mark = p; + break; + case header_value: + header_value_mark = p; + break; + case req_path: + case req_schema: + case req_schema_slash: + case req_schema_slash_slash: + case req_host_start: + case req_host_v6_start: + case req_host_v6: + case req_host_v6_end: + case req_host: + case req_port_start: + case req_port: + case req_query_string_start: + case req_query_string: + case req_fragment_start: + case req_fragment: + url_mark = p; + break; + } + boolean reexecute = false; + int pe = 0; + byte ch = 0; + int chi = 0; + byte c = -1; + int to_read = 0; + + // this is where the work gets done, traverse the available data... + while (data.position() != data.limit() || reexecute) { +// p(state + ": r: " + reexecute + " :: " +p ); + + if(!reexecute){ + p = data.position(); + pe = data.limit(); + ch = data.get(); // the current character to process. + chi = ch & 0xff; // utility, ch without signedness for table lookups. + c = -1; // utility variably used for up- and downcasing etc. + to_read = 0; // used to keep track of how much of body, etc. is left to read + + if (parsing_header(state)) { + ++nread; + if (nread > HTTP_MAX_HEADER_SIZE) { + return error(settings, "possible buffer overflow", data); + } + } + } + reexecute = false; +// p(state + " ::: " + ch + " : " + (((CR == ch) || (LF == ch)) ? ch : ("'" + (char)ch + "'")) +": "+p ); + + switch (state) { + /* + * this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + case dead: + if (CR == ch || LF == ch){ + break; + } + return error(settings, "Connection already closed", data); + + + + case start_req_or_res: + if (CR == ch || LF == ch){ + break; + } + flags = 0; + content_length = -1; + + if (H == ch) { + state = State.res_or_resp_H; + } else { + type = ParserType.HTTP_REQUEST; + method = start_req_method_assign(ch); + if (null == method) { + return error(settings, "invalid method", data); + } + index = 1; + state = State.req_method; + } + settings.call_on_message_begin(this); + break; + + + + case res_or_resp_H: + if (T == ch) { + type = ParserType.HTTP_RESPONSE; + state = State.res_HT; + } else { + if (E != ch) { + return error(settings, "not E", data); + } + type = ParserType.HTTP_REQUEST; + method = HTTPMethod.HTTP_HEAD; + index = 2; + state = State.req_method; + } + break; + + + + case start_res: + flags = 0; + content_length = -1; + + switch(ch) { + case H: + state = State.res_H; + break; + case CR: + case LF: + break; + default: + return error(settings, "Not H or CR/LF", data); + } + + settings.call_on_message_begin(this); + break; + + + + case res_H: + if (strict && T != ch) { + return error(settings, "Not T", data); + } + state = State.res_HT; + break; + case res_HT: + if (strict && T != ch) { +return error(settings, "Not T2", data); + } + state = State.res_HTT; + break; + case res_HTT: + if (strict && P != ch) { +return error(settings, "Not P", data); + } + state = State.res_HTTP; + break; + case res_HTTP: + if (strict && SLASH != ch) { +return error(settings, "Not '/'", data); + } + state = State.res_first_http_major; + break; + + + + case res_first_http_major: + if (!isDigit(ch)) { +return error(settings, "Not a digit", data); + } + http_major = (int) ch - 0x30; + state = State.res_http_major; + break; + + /* major HTTP version or dot */ + case res_http_major: + if (DOT == ch) { + state = State.res_first_http_minor; + break; + } + if (!isDigit(ch)) { +return error(settings, "Not a digit", data); + } + http_major *= 10; + http_major += (ch - 0x30); + + if (http_major > 999) { +return error(settings, "invalid http major version: ", data); + } + break; + + /* first digit of minor HTTP version */ + case res_first_http_minor: + if (!isDigit(ch)) { +return error(settings, "Not a digit", data); + } + http_minor = (int)ch - 0x30; + state = State.res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case res_http_minor: + if (SPACE == ch) { + state = State.res_first_status_code; + break; + } + if (!isDigit(ch)) { +return error(settings, "Not a digit", data); + } + http_minor *= 10; + http_minor += (ch - 0x30); + if (http_minor > 999) { +return error(settings, "invalid http minor version: ", data); + } + break; + + + + case res_first_status_code: + if (!isDigit(ch)) { + if (SPACE == ch) { + break; + } +return error(settings, "Not a digit (status code)", data); + } + status_code = (int)ch - 0x30; + state = State.res_status_code; + break; + + case res_status_code: + if (!isDigit(ch)) { + switch(ch) { + case SPACE: + state = State.res_status; + break; + case CR: + state = State.res_line_almost_done; + break; + case LF: + state = State.header_field_start; + break; + default: +return error(settings, "not a valid status code", data); + } + break; + } + status_code *= 10; + status_code += (int)ch - 0x30; + if (status_code > 999) { +return error(settings, "ridiculous status code:", data); + } + + if (status_code > 99) { + settings.call_on_status_complete(this); + } + break; + + case res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this + * we are not men, we are devo. */ + + if (CR == ch) { + state = State.res_line_almost_done; + break; + } + if (LF == ch) { + state = State.header_field_start; + break; + } + break; + + case res_line_almost_done: + if (strict && LF != ch) { +return error(settings, "not LF", data); + } + state = State.header_field_start; + break; + + + + case start_req: + if (CR==ch || LF == ch) { + break; + } + flags = 0; + content_length = -1; + + if(!isAtoZ(ch)){ + return error(settings, "invalid method", data); + } + + method = start_req_method_assign(ch); + if (null == method) { + return error(settings, "invalid method", data); + } + index = 1; + state = State.req_method; + + settings.call_on_message_begin(this); + break; + + + + case req_method: + if (0 == ch) { + return error(settings, "NULL in method", data); + } + + byte [] arr = method.bytes; + + if (SPACE == ch && index == arr.length) { + state = State.req_spaces_before_url; + } else if (arr[index] == ch) { + // wuhu! + } else if (HTTPMethod.HTTP_CONNECT == method) { + if (1 == index && H == ch) { + method = HTTPMethod.HTTP_CHECKOUT; + } else if (2 == index && P == ch) { + method = HTTPMethod.HTTP_COPY; + } + } else if (HTTPMethod.HTTP_MKCOL == method) { + if (1 == index && O == ch) { + method = HTTPMethod.HTTP_MOVE; + } else if (1 == index && E == ch) { + method = HTTPMethod.HTTP_MERGE; + } else if (1 == index && DASH == ch) { /* M-SEARCH */ + method = HTTPMethod.HTTP_MSEARCH; + } else if (2 == index && A == ch) { + method = HTTPMethod.HTTP_MKACTIVITY; + } + } else if (1 == index && HTTPMethod.HTTP_POST == method) { + if(R == ch) { + method = HTTPMethod.HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + }else if(U == ch){ + method = HTTPMethod.HTTP_PUT; /* or HTTP_PURGE */ + }else if(A == ch){ + method = HTTPMethod.HTTP_PATCH; + } + } else if (2 == index) { + if(HTTPMethod.HTTP_PUT == method) { + if(R == ch){ + method = HTTPMethod.HTTP_PURGE; + } + }else if(HTTPMethod.HTTP_UNLOCK == method){ + if(S == ch){ + method = HTTPMethod.HTTP_UNSUBSCRIBE; + } + } + }else if(4 == index && HTTPMethod.HTTP_PROPFIND == method && P == ch){ + method = HTTPMethod.HTTP_PROPPATCH; + } else { + return error(settings, "Invalid HTTP method", data); + } + + ++index; + break; + + + + /******************* URL *******************/ + case req_spaces_before_url: + if (SPACE == ch) { + break; + } + url_mark = p; + if(HTTPMethod.HTTP_CONNECT == method){ + state = req_host_start; + } + + state = parse_url_char(ch); + if(state == dead){ + return error(settings, "Invalid something", data); + } + break; + + + case req_schema: + case req_schema_slash: + case req_schema_slash_slash: + case req_host_start: + case req_host_v6_start: + case req_host_v6: + case req_port_start: + switch (ch) { + /* No whitespace allowed here */ + case SPACE: + case CR: + case LF: + return error(settings, "unexpected char in path", data); + default: + state = parse_url_char(ch); + if(dead == state){ + return error(settings, "unexpected char in path", data); + } + } + break; + + case req_host: + case req_host_v6_end: + case req_port: + case req_path: + case req_query_string_start: + case req_query_string: + case req_fragment_start: + case req_fragment: + switch (ch) { + case SPACE: + settings.call_on_url(this, data, url_mark, p-url_mark); + settings.call_on_path(this, data, url_mark, p - url_mark); + url_mark = -1; + state = State.req_http_start; + break; + case CR: + case LF: + http_major = 0; + http_minor = 9; + state = (CR == ch) ? req_line_almost_done : header_field_start; + settings.call_on_url(this, data, url_mark, p-url_mark); //TODO check params!!! + settings.call_on_path(this, data, url_mark, p-url_mark); + url_mark = -1; + break; + default: + state = parse_url_char(ch); + if(dead == state){ + return error(settings, "unexpected char in path", data); + } + } + break; + /******************* URL *******************/ + + + + /******************* HTTP 1.1 *******************/ + case req_http_start: + switch (ch) { + case H: + state = State.req_http_H; + break; + case SPACE: + break; + default: + return error(settings, "error in req_http_H", data); + } + break; + + case req_http_H: + if (strict && T != ch) { + return error(settings, "unexpected char", data); + } + state = State.req_http_HT; + break; + + case req_http_HT: + if (strict && T != ch) { + return error(settings, "unexpected char", data); + } + state = State.req_http_HTT; + break; + + case req_http_HTT: + if (strict && P != ch) { + return error(settings, "unexpected char", data); + } + state = State.req_http_HTTP; + break; + + case req_http_HTTP: + if (strict && SLASH != ch) { + return error(settings, "unexpected char", data); + } + state = req_first_http_major; + break; + + /* first digit of major HTTP version */ + case req_first_http_major: + if (!isDigit(ch)) { +return error(settings, "non digit in http major", data); + } + http_major = (int)ch - 0x30; + state = State.req_http_major; + break; + + /* major HTTP version or dot */ + case req_http_major: + if (DOT == ch) { + state = State.req_first_http_minor; + break; + } + + if (!isDigit(ch)) { +return error(settings, "non digit in http major", data); + } + + http_major *= 10; + http_major += (int)ch - 0x30; + + if (http_major > 999) { +return error(settings, "ridiculous http major", data); + }; + break; + + /* first digit of minor HTTP version */ + case req_first_http_minor: + if (!isDigit(ch)) { +return error(settings, "non digit in http minor", data); + } + http_minor = (int)ch - 0x30; + state = State.req_http_minor; + break; + + case req_http_minor: + if (ch == CR) { + state = State.req_line_almost_done; + break; + } + + if (ch == LF) { + state = State.header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!isDigit(ch)) { +return error(settings, "non digit in http minor", data); + } + + http_minor *= 10; + http_minor += (int)ch - 0x30; + + + if (http_minor > 999) { +return error(settings, "ridiculous http minor", data); + }; + + break; + + /* end of request line */ + case req_line_almost_done: + { + if (ch != LF) { +return error(settings, "missing LF after request line", data); + } + state = header_field_start; + break; + } + + /******************* HTTP 1.1 *******************/ + + + + /******************* Header *******************/ + case header_field_start: + { + if (ch == CR) { + state = headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + state = State.headers_almost_done; + reexecute = true; + break; + } + + c = token(ch); + + if (0 == c) { + return error(settings, "invalid char in header:", data); + } + + header_field_mark = p; + + index = 0; + state = State.header_field; + + switch (c) { + case C: + header_state = HState.C; + break; + + case P: + header_state = HState.matching_proxy_connection; + break; + + case T: + header_state = HState.matching_transfer_encoding; + break; + + case U: + header_state = HState.matching_upgrade; + break; + + default: + header_state = HState.general; + break; + } + break; + } + + + + case header_field: + { + c = token(ch); + if (0 != c) { + switch (header_state) { + case general: + break; + + case C: + index++; + header_state = (O == c ? HState.CO : HState.general); + break; + + case CO: + index++; + header_state = (N == c ? HState.CON : HState.general); + break; + + case CON: + index++; + switch (c) { + case N: + header_state = HState.matching_connection; + break; + case T: + header_state = HState.matching_content_length; + break; + default: + header_state = HState.general; + break; + } + break; + + /* connection */ + + case matching_connection: + index++; + if (index > CONNECTION.length || c != CONNECTION[index]) { + header_state = HState.general; + } else if (index == CONNECTION.length-1) { + header_state = HState.connection; + } + break; + + /* proxy-connection */ + + case matching_proxy_connection: + index++; + if (index > PROXY_CONNECTION.length || c != PROXY_CONNECTION[index]) { + header_state = HState.general; + } else if (index == PROXY_CONNECTION.length-1) { + header_state = HState.connection; + } + break; + + /* content-length */ + + case matching_content_length: + index++; + if (index > CONTENT_LENGTH.length || c != CONTENT_LENGTH[index]) { + header_state = HState.general; + } else if (index == CONTENT_LENGTH.length-1) { + header_state = HState.content_length; + } + break; + + /* transfer-encoding */ + + case matching_transfer_encoding: + index++; + if (index > TRANSFER_ENCODING.length || c != TRANSFER_ENCODING[index]) { + header_state = HState.general; + } else if (index == TRANSFER_ENCODING.length-1) { + header_state = HState.transfer_encoding; + } + break; + + /* upgrade */ + + case matching_upgrade: + index++; + if (index > UPGRADE.length || c != UPGRADE[index]) { + header_state = HState.general; + } else if (index == UPGRADE.length-1) { + header_state = HState.upgrade; + } + break; + + case connection: + case content_length: + case transfer_encoding: + case upgrade: + if (SPACE != ch) header_state = HState.general; + break; + + default: +return error(settings, "Unknown Header State", data); + } // switch: header_state + break; + } // 0 != c + + if (COLON == ch) { + settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark); + header_field_mark = -1; + + state = State.header_value_start; + break; + } + + if (CR == ch) { + state = State.header_almost_done; + settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark); + + header_field_mark = -1; + break; + } + + if (ch == LF) { + settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark); + header_field_mark = -1; + + state = State.header_field_start; + break; + } + +return error(settings, "invalid header field", data); + } + + + + case header_value_start: + { + if ((SPACE == ch) || (TAB == ch)) break; + + header_value_mark = p; + + state = State.header_value; + index = 0; + + + if (CR == ch) { + settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark); + header_value_mark = -1; + + header_state = HState.general; + state = State.header_almost_done; + break; + } + + if (LF == ch) { + settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark); + header_value_mark = -1; + + state = State.header_field_start; + break; + } + + + c = upper(ch); + + switch (header_state) { + case upgrade: + flags |= F_UPGRADE; + header_state = HState.general; + break; + + case transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if (C == c) { + header_state = HState.matching_transfer_encoding_chunked; + } else { + header_state = HState.general; + } + break; + + case content_length: + if (!isDigit(ch)) { +return error(settings, "Content-Length not numeric", data); + } + content_length = (int)ch - 0x30; + break; + + case connection: + /* looking for 'Connection: keep-alive' */ + if (K == c) { + header_state = HState.matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (C == c) { + header_state = HState.matching_connection_close; + } else { + header_state = HState.general; + } + break; + + default: + header_state = HState.general; + break; + } + break; + } // header value start + + + + case header_value: + { + + if (CR == ch) { + settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark); + header_value_mark = -1; + + state = State.header_almost_done; + break; + } + + if (LF == ch) { + settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark); + header_value_mark = -1; + state = header_almost_done; + reexecute = true; + break; + } + + c = upper(ch); + switch (header_state) { + case general: + break; + + case connection: + case transfer_encoding: +return error(settings, "Shouldn't be here", data); + + case content_length: + if (SPACE == ch) { + break; + } + if (!isDigit(ch)) { +return error(settings, "Content-Length not numeric", data); + } + + long t = content_length; + t *= 10; + t += (long)ch - 0x30; + + /* Overflow? */ + // t will wrap and become negative ... + if (t < content_length) { + return error(settings, "Invalid content length", data); + } + content_length = t; + break; + + /* Transfer-Encoding: chunked */ + case matching_transfer_encoding_chunked: + index++; + if (index > CHUNKED.length || c != CHUNKED[index]) { + header_state = HState.general; + } else if (index == CHUNKED.length-1) { + header_state = HState.transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case matching_connection_keep_alive: + index++; + if (index > KEEP_ALIVE.length || c != KEEP_ALIVE[index]) { + header_state = HState.general; + } else if (index == KEEP_ALIVE.length-1) { + header_state = HState.connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case matching_connection_close: + index++; + if (index > CLOSE.length || c != CLOSE[index]) { + header_state = HState.general; + } else if (index == CLOSE.length-1) { + header_state = HState.connection_close; + } + break; + + case transfer_encoding_chunked: + case connection_keep_alive: + case connection_close: + if (SPACE != ch) header_state = HState.general; + break; + + default: + state = State.header_value; + header_state = HState.general; + break; + } + break; + } // header_value + + + + case header_almost_done: + if (!header_almost_done(ch)) { + return error(settings, "incorrect header ending, expecting LF", data); + } + break; + + case header_value_lws: + if (SPACE == ch || TAB == ch ){ + state = header_value_start; + } else { + state = header_field_start; + reexecute = true; + } + break; + + case headers_almost_done: + if (LF != ch) { + return error(settings, "header not properly completed", data); + } + if (0 != (flags & F_TRAILING)) { + /* End of a chunked request */ + state = new_message(); + settings.call_on_headers_complete(this); + settings.call_on_message_complete(this); + break; + } + + state = headers_done; + + if (0 != (flags & F_UPGRADE) || HTTPMethod.HTTP_CONNECT == method) { + upgrade = true; + } + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + */ + + /* (responses to HEAD request contain a CONTENT-LENGTH header + * but no content) + * + * Consider what to do here: I don't like the idea of the callback + * interface having a different contract in the case of HEAD + * responses. The alternatives would be either to: + * + * a.) require the header_complete callback to implement a different + * interface or + * + * b.) provide an overridden execute(bla, bla, boolean + * parsingHeader) implementation ... + */ + + /*TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO */ + if (null != settings.on_headers_complete) { + settings.call_on_headers_complete(this); + //return; + } + + // if (null != settings.on_headers_complete) { + // switch (settings.on_headers_complete.cb(parser)) { + // case 0: + // break; + // + // case 1: + // flags |= F_SKIPBODY; + // break; + // + // default: + // return p - data; /* Error */ // TODO // RuntimeException ? + // } + // } + reexecute = true; + break; + + case headers_done: + if (strict && (LF != ch)) { + return error(settings, "STRICT CHECK", data); //TODO correct error msg + } + + nread = 0; + + // Exit, the rest of the connect is in a different protocol. + if (upgrade) { + state = new_message(); + settings.call_on_message_complete(this); + return data.position()-this.p_start; + } + + if (0 != (flags & F_SKIPBODY)) { + state = new_message(); + settings.call_on_message_complete(this); + } else if (0 != (flags & F_CHUNKED)) { + /* chunked encoding - ignore Content-Length header */ + state = State.chunk_size_start; + } else { + if (content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + state = new_message(); + settings.call_on_message_complete(this); + } else if (content_length != -1) { + /* Content-Length header given and non-zero */ + state = State.body_identity; + } else { + if (type == ParserType.HTTP_REQUEST || !http_message_needs_eof()) { + /* Assume content-length 0 - read the next */ + state = new_message(); + settings.call_on_message_complete(this); + } else { + /* Read body until EOF */ + state = State.body_identity_eof; + } + } + } + + break; + /******************* Header *******************/ + + + + + /******************* Body *******************/ + case body_identity: + to_read = min(pe - p, content_length); //TODO change to use buffer? + body_mark = p; + + if (to_read > 0) { + settings.call_on_body(this, data, p, to_read); + data.position(p+to_read); + content_length -= to_read; + + if (content_length == 0) { + state = message_done; + reexecute = true; + } + } + break; + + + + case body_identity_eof: + to_read = pe - p; // TODO change to use buffer ? + if (to_read > 0) { + settings.call_on_body(this, data, p, to_read); + data.position(p+to_read); + } + break; + + case message_done: + state = new_message(); + settings.call_on_message_complete(this); + break; + /******************* Body *******************/ + + + + /******************* Chunk *******************/ + case chunk_size_start: + if (1 != this.nread) { +return error(settings, "nread != 1 (chunking)", data); + + } + if (0 == (flags & F_CHUNKED)) { +return error(settings, "not chunked", data); + } + + c = UNHEX[chi]; + if (c == -1) { +return error(settings, "invalid hex char in chunk content length", data); + } + content_length = c; + state = State.chunk_size; + break; + + + + case chunk_size: + if (0 == (flags & F_CHUNKED)) { + return error(settings, "not chunked", data); + } + + if (CR == ch) { + state = State.chunk_size_almost_done; + break; + } + + c = UNHEX[chi]; + + if (c == -1) { + if (SEMI == ch || SPACE == ch) { + state = State.chunk_parameters; + break; + } + return error(settings, "invalid hex char in chunk content length", data); + } + long t = content_length; + + t *= 16; + t += c; + if(t < content_length){ + return error(settings, "invalid content length", data); + } + content_length = t; + break; + + + + case chunk_parameters: + if (0 == (flags & F_CHUNKED)) { +return error(settings, "not chunked", data); + } + /* just ignore this shit. TODO check for overflow */ + if (CR == ch) { + state = State.chunk_size_almost_done; + break; + } + break; + + + + case chunk_size_almost_done: + if (0 == (flags & F_CHUNKED)) { +return error(settings, "not chunked", data); + } + if (strict && LF != ch) { +return error(settings, "expected LF at end of chunk size", data); + } + + this.nread = 0; + + if (0 == content_length) { + flags |= F_TRAILING; + state = State.header_field_start; + } else { + state = State.chunk_data; + } + break; + + + + case chunk_data: + //TODO Apply changes from C version for s_chunk_data + if (0 == (flags & F_CHUNKED)) { + return error(settings, "not chunked", data); + } + + to_read = min(pe-p, content_length); + if (to_read > 0) { + settings.call_on_body(this, data, p, to_read); + data.position(p+to_read); + } + + if (to_read == content_length) { + state = State.chunk_data_almost_done; + } + + content_length -= to_read; + break; + + + + case chunk_data_almost_done: + if (0 == (flags & F_CHUNKED)) { +return error(settings, "not chunked", data); + } + if (strict && CR != ch) { +return error(settings, "chunk data terminated incorrectly, expected CR", data); + } + state = State.chunk_data_done; + //TODO CALLBACK_DATA(body) + // settings.call_on_body(this, data,p,?); + break; + + + + case chunk_data_done: + if (0 == (flags & F_CHUNKED)) { +return error(settings, "not chunked", data); + } + if (strict && LF != ch) { +return error(settings, "chunk data terminated incorrectly, expected LF", data); + } + state = State.chunk_size_start; + break; + /******************* Chunk *******************/ + + + + default: +return error(settings, "unhandled state", data); + + } // switch + } // while + + p = data.position(); + + + /* Reaching this point assumes that we only received part of a + * message, inform the callbacks about the progress made so far*/ + + settings.call_on_header_field(this, data, header_field_mark, p-header_field_mark); + settings.call_on_header_value(this, data, header_value_mark, p-header_value_mark); + settings.call_on_url (this, data, url_mark, p-url_mark); + settings.call_on_path (this, data, url_mark, p-url_mark); + + return data.position()-this.p_start; + } // execute + + int error (ParserSettings settings, String mes, ByteBuffer data) { + settings.call_on_error(this, mes, data, this.p_start); + this.state = State.dead; + return data.position()-this.p_start; + } + + public boolean http_message_needs_eof() { + if(type == ParserType.HTTP_REQUEST){ + return false; + } + /* See RFC 2616 section 4.4 */ + if ((status_code / 100 == 1) || /* 1xx e.g. Continue */ + (status_code == 204) || /* No Content */ + (status_code == 304) || /* Not Modified */ + (flags & F_SKIPBODY) != 0) { /* response to a HEAD request */ + return false; + } + if ((flags & F_CHUNKED) != 0 || content_length != -1) { + return false; + } + + return true; + } + + /* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ + public boolean http_should_keep_alive() { + if (http_major > 0 && http_minor > 0) { + /* HTTP/1.1 */ + if ( 0 != (flags & F_CONNECTION_CLOSE) ) { + return false; + } + } else { + /* HTTP/1.0 or earlier */ + if ( 0 == (flags & F_CONNECTION_KEEP_ALIVE) ) { + return false; + } + } + return !http_message_needs_eof(); + } + + public int parse_url(ByteBuffer data, boolean is_connect, HTTPParserUrl u) { + + UrlFields uf = UrlFields.UF_MAX; + UrlFields old_uf = UrlFields.UF_MAX; + u.port = 0; + u.field_set = 0; + state = (is_connect ? State.req_host_start : State.req_spaces_before_url); + int p_init = data.position(); + int p = 0; + byte ch = 0; + while (data.position() != data.limit()) { + p = data.position(); + ch = data.get(); + state = parse_url_char(ch); + switch(state) { + case dead: + return 1; + + /* Skip delimeters */ + case req_schema_slash: + case req_schema_slash_slash: + case req_host_start: + case req_host_v6_start: + case req_host_v6_end: + case req_port_start: + case req_query_string_start: + case req_fragment_start: + continue; + + case req_schema: + uf = UrlFields.UF_SCHEMA; + break; + + case req_host: + case req_host_v6: + uf = UrlFields.UF_HOST; + break; + + case req_port: + uf = UrlFields.UF_PORT; + break; + + case req_path: + uf = UrlFields.UF_PATH; + break; + + case req_query_string: + uf = UrlFields.UF_QUERY; + break; + + case req_fragment: + uf = UrlFields.UF_FRAGMENT; + break; + + default: + return 1; + } + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u.field_data[uf.getIndex()].len++; + continue; + } + + u.field_data[uf.getIndex()].off = p - p_init; + u.field_data[uf.getIndex()].len = 1; + + u.field_set |= (1 << uf.getIndex()); + old_uf = uf; + + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u.field_set != ((1 << UrlFields.UF_HOST.getIndex())|(1 << UrlFields.UF_PORT.getIndex()))) { + return 1; + } + + /* Make sure we don't end somewhere unexpected */ + switch (state) { + case req_host_v6_start: + case req_host_v6: + case req_host_v6_end: + case req_host: + case req_port_start: + return 1; + default: + break; + } + + if (0 != (u.field_set & (1 << UrlFields.UF_PORT.getIndex()))) { + /* Don't bother with endp; we've already validated the string */ + int v = strtoi(data, p_init + u.field_data[UrlFields.UF_PORT.getIndex()].off); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u.port = v; + } + + return 0; + } + + //hacky reimplementation of srttoul, tailored for our simple needs + //we only need to parse port val, so no negative values etc + int strtoi(ByteBuffer data, int start_pos) { + data.position(start_pos); + byte ch; + String str = ""; + while(data.position() < data.limit()) { + ch = data.get(); + if(Character.isWhitespace((char)ch)){ + continue; + } + if(isDigit(ch)){ + str = str + (char)ch; //TODO replace with something less hacky + }else{ + break; + } + } + return Integer.parseInt(str); + } + + boolean isDigit(byte b) { + if (b >= 0x30 && b <=0x39) { + return true; + } + return false; + } + + boolean isHex(byte b) { + return isDigit(b) || (lower(b) >= 0x61 /*a*/ && lower(b) <= 0x66 /*f*/); + } + + boolean isAtoZ(byte b) { + byte c = lower(b); + return (c>= 0x61 /*a*/ && c <= 0x7a /*z*/); + } + + + byte lower (byte b) { + return (byte)(b|0x20); + } + + byte upper(byte b) { + char c = (char)(b); + return (byte)Character.toUpperCase(c); + } + + byte token(byte b) { + if(!strict){ + return (b == (byte)' ') ? (byte)' ' : (byte)tokens[b] ; + }else{ + return (byte)tokens[b]; + } + } + + boolean isHostChar(byte ch){ + if(!strict){ + return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch || UNDER == ch ; + }else{ + return (isAtoZ(ch)) || isDigit(ch) || DOT == ch || DASH == ch; + } + } + + boolean isNormalUrlChar(int chi) { + if(!strict){ + return (chi > 0x80) || normal_url_char[chi]; + }else{ + return normal_url_char[chi]; + } + } + + HTTPMethod start_req_method_assign(byte c){ + switch (c) { + case C: return HTTPMethod.HTTP_CONNECT; /* or COPY, CHECKOUT */ + case D: return HTTPMethod.HTTP_DELETE; + case G: return HTTPMethod.HTTP_GET; + case H: return HTTPMethod.HTTP_HEAD; + case L: return HTTPMethod.HTTP_LOCK; + case M: return HTTPMethod.HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ + case N: return HTTPMethod.HTTP_NOTIFY; + case O: return HTTPMethod.HTTP_OPTIONS; + case P: return HTTPMethod.HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + case R: return HTTPMethod.HTTP_REPORT; + case S: return HTTPMethod.HTTP_SUBSCRIBE; + case T: return HTTPMethod.HTTP_TRACE; + case U: return HTTPMethod.HTTP_UNLOCK; /* or UNSUBSCRIBE */ + } + return null; // ugh. + } + + boolean header_almost_done(byte ch) { + if (strict && LF != ch) { + return false; + } + + state = State.header_value_lws; + // TODO java enums support some sort of bitflag mechanism !? + switch (header_state) { + case connection_keep_alive: + flags |= F_CONNECTION_KEEP_ALIVE; + break; + case connection_close: + flags |= F_CONNECTION_CLOSE; + break; + case transfer_encoding_chunked: + flags |= F_CHUNKED; + break; + default: + break; + } + return true; + } + +// boolean headers_almost_done (byte ch, ParserSettings settings) { +// } // headers_almost_done + + + final int min (int a, int b) { + return a < b ? a : b; + } + + final int min (int a, long b) { + return a < b ? a : (int)b; + } + + /* probably not the best place to hide this ... */ + public boolean HTTP_PARSER_STRICT; + State new_message() { + if (HTTP_PARSER_STRICT){ + return http_should_keep_alive() ? start_state() : State.dead; + } else { + return start_state(); + } + + } + + State start_state() { + return type == ParserType.HTTP_REQUEST ? State.start_req : State.start_res; + } + + + boolean parsing_header(State state) { + + switch (state) { + case chunk_data : + case chunk_data_almost_done : + case chunk_data_done : + case body_identity : + case body_identity_eof : + case message_done : + return false; + + } + return true; + } + + /* "Dial C for Constants" */ + static class C { + static final int HTTP_MAX_HEADER_SIZE = 80 * 1024; + + static final int F_CHUNKED = 1 << 0; + static final int F_CONNECTION_KEEP_ALIVE = 1 << 1; + static final int F_CONNECTION_CLOSE = 1 << 2; + static final int F_TRAILING = 1 << 3; + static final int F_UPGRADE = 1 << 4; + static final int F_SKIPBODY = 1 << 5; + + static final byte [] UPCASE = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x2d,0x00,0x2f, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, 0x38,0x39,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, 0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x5f, + 0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, 0x58,0x59,0x5a,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + }; + static final byte [] CONNECTION = { + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, + }; + static final byte [] PROXY_CONNECTION = { + 0x50, 0x52, 0x4f, 0x58, 0x59, 0x2d, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, + }; + static final byte [] CONTENT_LENGTH = { + 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x2d, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48, + }; + static final byte [] TRANSFER_ENCODING = { + 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x2d, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, + }; + static final byte [] UPGRADE = { + 0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x45, + }; + static final byte [] CHUNKED = { + 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x45, 0x44, + }; + static final byte [] KEEP_ALIVE = { + 0x4b, 0x45, 0x45, 0x50, 0x2d, 0x41, 0x4c, 0x49, 0x56, 0x45, + }; + static final byte [] CLOSE = { + 0x43, 0x4c, 0x4f, 0x53, 0x45, + }; + + /* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ + + static final char [] tokens = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0 , +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'X', 'Y', 'Z', 0, 0, 0, 0, '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'X', 'Y', 'Z', 0, '|', 0, '~', 0, +/* hi bit set, not ascii */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; + + static final byte [] UNHEX = + { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + static final boolean [] normal_url_char = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + false, false, false, false, false, false, false, false, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + false, false, false, false, false, false, false, false, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + false, false, false, false, false, false, false, false, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + false, false, false, false, false, false, false, false, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + false, true, true, false, true, true, true, true, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + true, true, true, true, true, true, true, true, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + true, true, true, true, true, true, true, true, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + true, true, true, true, true, true, true, false, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + true, true, true, true, true, true, true, true, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + true, true, true, true, true, true, true, true, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + true, true, true, true, true, true, true, true, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + true, true, true, true, true, true, true, true, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + true, true, true, true, true, true, true, true, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + true, true, true, true, true, true, true, true, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + true, true, true, true, true, true, true, true, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + true, true, true, true, true, true, true, false, + +/* hi bit set, not ascii */ +/* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8 + * encoded paths. This is out of spec, but clients generate this and most other + * HTTP servers support it. We should, too. */ + + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, + + }; + + public static final byte A = 0x41; + public static final byte B = 0x42; + public static final byte C = 0x43; + public static final byte D = 0x44; + public static final byte E = 0x45; + public static final byte F = 0x46; + public static final byte G = 0x47; + public static final byte H = 0x48; + public static final byte I = 0x49; + public static final byte J = 0x4a; + public static final byte K = 0x4b; + public static final byte L = 0x4c; + public static final byte M = 0x4d; + public static final byte N = 0x4e; + public static final byte O = 0x4f; + public static final byte P = 0x50; + public static final byte Q = 0x51; + public static final byte R = 0x52; + public static final byte S = 0x53; + public static final byte T = 0x54; + public static final byte U = 0x55; + public static final byte V = 0x56; + public static final byte W = 0x57; + public static final byte X = 0x58; + public static final byte Y = 0x59; + public static final byte Z = 0x5a; + public static final byte UNDER = 0x5f; + public static final byte CR = 0x0d; + public static final byte LF = 0x0a; + public static final byte DOT = 0x2e; + public static final byte SPACE = 0x20; + public static final byte TAB = 0x09; + public static final byte SEMI = 0x3b; + public static final byte COLON = 0x3a; + public static final byte HASH = 0x23; + public static final byte QMARK = 0x3f; + public static final byte SLASH = 0x2f; + public static final byte DASH = 0x2d; + public static final byte STAR = 0x2a; + public static final byte NULL = 0x00; + } + + enum State { + + dead + + , start_req_or_res + , res_or_resp_H + , start_res + , res_H + , res_HT + , res_HTT + , res_HTTP + , res_first_http_major + , res_http_major + , res_first_http_minor + , res_http_minor + , res_first_status_code + , res_status_code + , res_status + , res_line_almost_done + + , start_req + + , req_method + , req_spaces_before_url + , req_schema + , req_schema_slash + , req_schema_slash_slash + , req_host_start + , req_host_v6_start + , req_host_v6 + , req_host_v6_end + , req_host + , req_port_start + , req_port + , req_path + , req_query_string_start + , req_query_string + , req_fragment_start + , req_fragment + , req_http_start + , req_http_H + , req_http_HT + , req_http_HTT + , req_http_HTTP + , req_first_http_major + , req_http_major + , req_first_http_minor + , req_http_minor + , req_line_almost_done + + , header_field_start + , header_field + , header_value_start + , header_value + , header_value_lws + + , header_almost_done + + , chunk_size_start + , chunk_size + , chunk_parameters + , chunk_size_almost_done + + , headers_almost_done + , headers_done +// This space intentionally not left blank, comment from c, for orientation... +// the c version uses <= s_header_almost_done in java, we list the states explicitly +// in `parsing_header()` +/* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + , chunk_data + , chunk_data_almost_done + , chunk_data_done + + , body_identity + , body_identity_eof + , message_done + + } + enum HState { + general + , C + , CO + , CON + + , matching_connection + , matching_proxy_connection + , matching_content_length + , matching_transfer_encoding + , matching_upgrade + + , connection + , content_length + , transfer_encoding + , upgrade + + , matching_transfer_encoding_chunked + , matching_connection_keep_alive + , matching_connection_close + + , transfer_encoding_chunked + , connection_keep_alive + , connection_close + } + public enum UrlFields { + UF_SCHEMA(0) + , UF_HOST(1) + , UF_PORT(2) + , UF_PATH(3) + , UF_QUERY(4) + , UF_FRAGMENT(5) + , UF_MAX(6); + + + private final int index; + + private UrlFields(int index) { + this.index = index; + } + public int getIndex() { + return index; + } + + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java new file mode 100644 index 0000000..1ebdd4f --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java @@ -0,0 +1,83 @@ +package http_parser.lolevel; +import java.nio.ByteBuffer; +import http_parser.HTTPException; +public class ParserSettings { + + public HTTPCallback on_message_begin; + public HTTPDataCallback on_path; + public HTTPDataCallback on_query_string; + public HTTPDataCallback on_url; + public HTTPDataCallback on_fragment; + public HTTPCallback on_status_complete; + public HTTPDataCallback on_header_field; + public HTTPDataCallback on_header_value; + public HTTPCallback on_headers_complete; + public HTTPDataCallback on_body; + public HTTPCallback on_message_complete; + public HTTPErrorCallback on_error; + + void call_on_message_begin (HTTPParser p) { + call_on(on_message_begin, p); + } + + void call_on_message_complete (HTTPParser p) { + call_on(on_message_complete, p); + } + + // this one is a little bit different: + // the current `position` of the buffer is the location of the + // error, `ini_pos` indicates where the position of + // the buffer when it was passed to the `execute` method of the parser, i.e. + // using this information and `limit` we'll know all the valid data + // in the buffer around the error we can use to print pretty error + // messages. + void call_on_error (HTTPParser p, String mes, ByteBuffer buf, int ini_pos) { + if (null != on_error) { + on_error.cb(p, mes, buf, ini_pos); + return; + } + // if on_error gets called it MUST throw an exception, else the parser + // will attempt to continue parsing, which it can't because it's + // in an invalid state. + throw new HTTPException(mes); + } + + void call_on_header_field (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_header_field, p, buf, pos, len); + } + void call_on_query_string (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_query_string, p, buf, pos, len); + } + void call_on_fragment (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_fragment, p, buf, pos, len); + } + void call_on_status_complete(HTTPParser p) { + call_on(on_status_complete, p); + } + void call_on_path (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_path, p, buf, pos, len); + } + void call_on_header_value (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_header_value, p, buf, pos, len); + } + void call_on_url (HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_url, p, buf, pos, len); + } + void call_on_body(HTTPParser p, ByteBuffer buf, int pos, int len) { + call_on(on_body, p, buf, pos, len); + } + void call_on_headers_complete(HTTPParser p) { + call_on(on_headers_complete, p); + } + void call_on (HTTPCallback cb, HTTPParser p) { + // cf. CALLBACK2 macro + if (null != cb) { + cb.cb(p); + } + } + void call_on (HTTPDataCallback cb, HTTPParser p, ByteBuffer buf, int pos, int len) { + if (null != cb && -1 != pos) { + cb.cb(p,buf,pos,len); + } + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java new file mode 100644 index 0000000..62f0a0e --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java @@ -0,0 +1,374 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.io.*; +import java.util.*; + +import http_parser.HTTPMethod; +import http_parser.HTTPParserUrl; +import http_parser.ParserType; +import http_parser.lolevel.TestLoaderNG.Header; +import http_parser.lolevel.TestLoaderNG.LastHeader; + +import primitive.collection.ByteList; + +import static http_parser.lolevel.Util.str; + +public class Message { + String name; + byte [] raw; + ParserType type; + HTTPMethod method; + int status_code; + String request_path; // byte [] ? + String request_url; + String fragment ; + String query_string; + byte [] body; + int body_size; + int num_headers; + LastHeader last_header_element; + Map header; + List
headers; + boolean should_keep_alive; + + byte[] upgrade; + boolean upgrade() { + return null != upgrade; + } + + int http_major; + int http_minor; + + boolean message_begin_called; + boolean headers_complete_called; + boolean message_complete_called; + boolean message_complete_on_eof; + + + Map parsed_header; + String currHField; + String currHValue; + byte [] pbody; + int num_called; + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("NAME: "); b.append(name);b.append("\n"); + b.append("type: "); b.append(type);b.append("\n"); + b.append("method: "); b.append(method);b.append("\n"); + b.append("status_code: "); b.append(status_code);b.append("\n"); + b.append("request_path: "); b.append(request_path);b.append("\n"); + b.append("request_url: "); b.append(request_url);b.append("\n"); + b.append("fragment: "); b.append(fragment);b.append("\n"); + b.append("query_string: "); b.append(query_string);b.append("\n"); + b.append("body:\n"); b.append(new String(body));b.append("\n"); + b.append("should_keep_alive: "); b.append(should_keep_alive);b.append("\n"); + b.append("upgrade: "); b.append(upgrade);b.append("\n"); + b.append("http_major: "); b.append(http_major);b.append("\n"); + b.append("http_minor: "); b.append(http_minor);b.append("\n"); + b.append("message_complete_called: "); b.append(message_complete_called);b.append("\n"); + return b.toString(); + } + + Message () { + this.header = new HashMap(); + this.headers = new LinkedList
(); + reset(); + } + /* + *prepare this Test Instance for reuse. + * */ + void reset () { + this.parsed_header = new HashMap(); + this.pbody = null; + this.num_called = 0; + + } + void check (boolean val, String mes) { + if (!val) { + //p(name+" : "+mes); + throw new RuntimeException(name+" : "+mes); + } + } + + + HTTPDataCallback getCB (final String value, final String mes, final TestSettings settings) { + return new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len){ + // if ("url".equals(mes)){ + // p("pos"+pos); + // p("len"+len); + // if (8==pos && 5 == len && "connect request".equals(name)) { + // //throw new RuntimeException(name); + // } + // } + //String str = str(b, pos, len); + ByteList list = settings.map.get(mes); + for (int i=0; i!=len; ++i) { + list.add(b.get(pos+i)); + } + //settings.map.put(mes, prev_val + str); + //check(value.equals(str), "incorrect "+mes+": "+str); + if (-1 == pos) { + throw new RuntimeException("he?"); + } + return 0; + } + }; + } + + void execute () { + p(name); + ByteBuffer buf = ByteBuffer.wrap(raw); + HTTPParser p = new HTTPParser(); + TestSettings s = settings(); + + + + p.execute(s, buf); + if (!p.upgrade) { + // call execute again, else parser can't know message is done + // if no content length is set. + p.execute(s, buf); + } + if (!s.success) { + throw new RuntimeException("Test: "+name+" failed"); + } + } // execute + + void execute_permutations() { + /* + |-|---------------| + |--|--------------| + |---|-------------| + (...) + |---------------|-| + |-----------------| + */ + p(name); + for (int i = 2; i != raw.length; ++i) { + // p(i); + HTTPParser p = new HTTPParser(); + TestSettings s = settings(); + ByteBuffer buf = ByteBuffer.wrap(raw); + int olimit = buf.limit(); + buf.limit(i); + + parse(p,s,buf); + if (!p.upgrade) { + buf.position(i); + buf.limit(olimit); + + parse(p,s,buf); + if (!p.upgrade) { + parse(p,s,buf); + } else { + if (!upgrade()) { + throw new RuntimeException("Test:"+name+"parsed as upgrade, is not"); + } + } + + } else { + if (!upgrade()) { + throw new RuntimeException("Test:"+name+"parsed as upgrade, is not"); + } + } + if (!s.success) { + p(this); + throw new RuntimeException("Test: "+name+" failed"); + } + reset(); + } + //System.exit(0); + } // execute_permutations + void parse(HTTPParser p, ParserSettings s, ByteBuffer b) { + //p("About to parse: "+b.position() + "->" + b.limit()); + p.execute(s, b); + } + + TestSettings settings() { + final TestSettings s = new TestSettings(); + s.on_url = getCB(request_url, "url", s); + s.on_message_begin = new HTTPCallback() { + public int cb (HTTPParser p) { + message_begin_called = true; + return -1; + } + }; + s.on_header_field = new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len){ + if (null != currHValue && null == currHField) { + throw new RuntimeException(name+": shouldn't happen"); + } + if (null != currHField) { + if (null == currHValue) { + currHField += str(b,pos,len); + return 0; + } else { + parsed_header.put(currHField, currHValue); + currHField = null; + currHValue = null; + } + } + currHField = str(b,pos,len); + return 0; + } + }; + s.on_header_value = new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len){ + if (null == currHField) { + throw new RuntimeException(name+" :shouldn't happen field"); + } + if (null == currHValue) { + currHValue = str(b,pos,len); + } else { + currHValue += str(b, pos, len); + } + return 0; + } + }; + s.on_headers_complete = new HTTPCallback() { + public int cb (HTTPParser p) { + headers_complete_called = true; + String parsed_path = null; + String parsed_query = null; + String parsed_url = null; + String parsed_frag = null; + + try { + parsed_url = new String(s.map.get("url").toArray(), "UTF8"); + + HTTPParserUrl u = new HTTPParserUrl(); + HTTPParser pp = new HTTPParser(); + ByteBuffer data = Util.buffer(parsed_url); + pp.parse_url(data,false, u); + + parsed_path = u.getFieldValue(HTTPParser.UrlFields.UF_PATH, data); + parsed_query = u.getFieldValue(HTTPParser.UrlFields.UF_QUERY, data); + parsed_frag = u.getFieldValue(HTTPParser.UrlFields.UF_FRAGMENT, data); + + } catch (java.io.UnsupportedEncodingException uee) { + throw new RuntimeException(uee); + } + + if (!request_path.equals(parsed_path)) { + throw new RuntimeException(name+": invalid path: "+parsed_path+" should be: "+request_path); + } + if (!query_string.equals(parsed_query)) { + throw new RuntimeException(name+": invalid query: "+parsed_query+" should be: "+query_string); + } + if (!request_url.equals(parsed_url)) { + throw new RuntimeException(">"+name+"<: invalid url: >"+parsed_url+"< should be: >"+request_url+"<"); + } + if (!fragment.equals(parsed_frag)) { + throw new RuntimeException(name+": invalid fragement: "+parsed_frag+" should be: "+fragment); + } + if (null != currHValue || null != currHField) { + if (null == currHField || null == currHValue) { + throw new RuntimeException("shouldn't happen"); + } + } + if (null != currHField) { + //p(currHField); + //p(">"+currHValue+"<"); + parsed_header.put(currHField, currHValue); + currHField = null; + currHValue = null; + } + + + return 0; + } + }; + // s.on_headers_complete = new HTTPCallback() { + // public int cb (HTTPParser p) { + // p("Complete:"+name); + // return 0; + // } + // }; + + s.on_body = new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len){ + int l = pbody == null ? len : len + pbody.length; + int off = pbody == null ? 0 : pbody.length; + byte [] nbody = new byte[l]; + + if (null != pbody) { + System.arraycopy(pbody, 0, nbody, 0, pbody.length); + } + + int saved = b.position(); + b.position(pos); + b.get(nbody, off, len); + b.position(saved); + pbody = nbody; + return 0; + } + }; + + s.on_message_complete = new HTTPCallback() { + public int cb(HTTPParser p) { + message_complete_called = true; + num_called += 1; + if ( p.http_minor != http_minor + || p.http_major != http_major + || p.status_code != status_code ) { + + throw new RuntimeException("major/minor/status_code mismatch"); + } + + //check headers + + if (header.keySet().size() != parsed_header.keySet().size()) { + p(parsed_header); + throw new RuntimeException(name+": different amount of headers"); + } + for (String key : header.keySet()) { + String pvalue = parsed_header.get(key); + if (!header.get(key).equals(pvalue)) { + throw new RuntimeException(name+" : different values for :"+key+" is >"+pvalue+"< should: >"+header.get(key)+"<"); + } + } + //check body + if (null == pbody && (null == body || body.length == 0 || body.length == 1)) { + s.success = true; + return 0; + } + if (null == pbody) { + throw new RuntimeException(name+": no body, should be: "+new String(body)); + } + if (pbody.length != body.length) { + p(pbody.length); + p(body.length); + p(new String(pbody)); + p(new String(body)); + throw new RuntimeException(name+": incorrect body length"); + } + for (int i = 0 ; i!= body.length; ++i) { + if (pbody[i] != body[i]) { + throw new RuntimeException("different body"); + } + } + s.success = true; + return 0; + } + }; + return s; + } // settings + static void p(Object o) { + System.out.println(o); + } + + static class TestSettings extends ParserSettings { + public boolean success; + Map map; + TestSettings () { + map = new HashMap(); + map.put("path", new ByteList()); + map.put("query_string", new ByteList()); + map.put("url", new ByteList()); + map.put("fragment", new ByteList()); + } + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java new file mode 100644 index 0000000..0e74021 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java @@ -0,0 +1,51 @@ +package http_parser.lolevel; + +import http_parser.HTTPParserUrl; +import static http_parser.lolevel.Util.*; + +public class ParseUrl { + public static void test(int i) { + HTTPParserUrl u = new HTTPParserUrl(); + HTTPParser p = new HTTPParser(); + Url test = Url.URL_TESTS[i]; +// System.out.println(":: " + test.name); + int rv = p.parse_url(Util.buffer(test.url),test.is_connect,u); + UnitTest.check_equals(rv, test.rv); + if(test.rv == 0){ + UnitTest.check_equals(u, test.u); + } + + } + public static void test() { + p(ParseUrl.class); + + for (int i = 0; i < Url.URL_TESTS.length; i++) { + test(i); + } + } + + static void usage() { + p("usage: [jre] http_parser.lolevel.ParseUrl [i]"); + p(" i : optional test case id"); + p("---------------------------------------------"); + p("Test Cases:"); + for (int i =0; i!= Url.URL_TESTS.length; ++i) { + p(" "+i+": "+Url.URL_TESTS[i].name); + } + } + + public static void main (String [] args) { + if (0 == args.length) { + test(); + } else { + try { + int i = Integer.parseInt(args[0]); + test(i); + } catch (Throwable t) { + t.printStackTrace(); + usage(); + } + + } + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java new file mode 100644 index 0000000..4367bbb --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java @@ -0,0 +1,69 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.util.*; + +import static http_parser.lolevel.Util.*; +import http_parser.*; + +import primitive.collection.ByteList; + +public class Requests { + + static void test_simple(String req, boolean should_pass) { + HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST); + ByteBuffer buf = buffer(req); + boolean passed = false; + int read = 0; + try { + parser.execute(Util.SETTINGS_NULL, buf); + passed = (read == req.length()); + read = parser.execute(Util.SETTINGS_NULL, Util.empty()); + passed &= (0 == read); + } catch (Throwable t) { + passed = false; + } + check(passed == should_pass); + } + static void simple_tests() { + test_simple("hello world", false); + test_simple("GET / HTP/1.1\r\n\r\n", false); + + test_simple("ASDF / HTTP/1.1\r\n\r\n", false); + test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", false); + test_simple("GETA / HTTP/1.1\r\n\r\n", false); + } + + public static void test () { + p(Requests.class); + simple_tests(); + + List all = TestLoaderNG.load("tests.dumped"); + List requests = new LinkedList(); + for (Message m : all) { + if (ParserType.HTTP_REQUEST == m.type) { + requests.add(m); + } + } + for (Message m : requests) { + test_message(m); + } + + for (int i = 0; i!= requests.size(); ++i) { + if (!requests.get(i).should_keep_alive) continue; + for (int j = 0; j!=requests.size(); ++j) { + if (!requests.get(j).should_keep_alive) continue; + for (int k = 0; k!= requests.size(); ++k) { + test_multiple3(requests.get(i), requests.get(j), requests.get(k)); + } + } + } + + // postpone test_scan + + } + + + + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java new file mode 100644 index 0000000..1cb71dc --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java @@ -0,0 +1,52 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.util.*; + +import static http_parser.lolevel.Util.*; +import http_parser.*; + +import primitive.collection.ByteList; + +public class Responses { + + + + public static void test () { + p(Responses.class); + List all = TestLoaderNG.load("tests.dumped"); + List responses = new LinkedList(); + for (Message m : all) { + if (ParserType.HTTP_RESPONSE == m.type) { + responses.add(m); + } + } + for (Message m : responses) { + test_message(m); + } + + for (int i = 0; i!= responses.size(); ++i) { + if (!responses.get(i).should_keep_alive) continue; + for (int j = 0; j!=responses.size(); ++j) { + if (!responses.get(j).should_keep_alive) continue; + for (int k = 0; k!= responses.size(); ++k) { + test_multiple3(responses.get(i), responses.get(j), responses.get(k)); + } + } + } + + // not sure what test_message_count_body does that test_message doesn't... + // Message m = find(responses, "404 no headers no body"); + // test_message_count_body(m); + // m = find(responses, "200 trailing space on chunked body"); + // test_message_count_body(m); + + // TODO test very large chunked response + + // test_scan is more or less the same as test_permutations, will implement later... + } + + + + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java new file mode 100644 index 0000000..6c35898 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java @@ -0,0 +1,16 @@ +package http_parser.lolevel; + + +public class Test { + public static void main (String [] args) { + UnitTest.test(); + TestHeaderOverflowError.test(); + TestNoOverflowLongBody.test(); + Responses.test(); + ParseUrl.test(); + Requests.test(); + Upgrade.test(); + WrongContentLength.test(); + } + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java new file mode 100644 index 0000000..ee47903 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java @@ -0,0 +1,48 @@ +package http_parser.lolevel; + +import java.nio.*; + +import static http_parser.lolevel.Util.*; + +public class TestHeaderOverflowError { + + public static void test (http_parser.ParserType type) { + HTTPParser parser = new HTTPParser(type); + ByteBuffer buf = getBytes(type); + + int numbytes = buf.limit(); + + parser.execute(Util.SETTINGS_NULL, buf); + + check(numbytes == buf.position()); + + buf = buffer("header-key: header-value\r\n"); + numbytes = buf.limit(); + for (int i = 0; i!= 1000; ++i) { + parser.execute(Util.SETTINGS_NULL, buf); + check(numbytes == buf.position()); + + buf.rewind(); + + } + } + + static ByteBuffer getBytes (http_parser.ParserType type) { + if (http_parser.ParserType.HTTP_BOTH == type) { + throw new RuntimeException("only HTTP_REQUEST and HTTP_RESPONSE"); + } + + if (http_parser.ParserType.HTTP_REQUEST == type) { + return buffer("GET / HTTP/1.1\r\n"); + } + return buffer("HTTP/1.0 200 OK\r\n"); + } + + public static void test () { + p(TestHeaderOverflowError.class); + test(http_parser.ParserType.HTTP_REQUEST); + test(http_parser.ParserType.HTTP_RESPONSE); + } + + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java new file mode 100644 index 0000000..329485d --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java @@ -0,0 +1,212 @@ +package http_parser.lolevel; +// name : 200 trailing space on chunked body +// raw : "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n" +// type : HTTP_RESPONSE +// method: HTTP_DELETE +// status code :200 +// request_path: +// request_url : +// fragment : +// query_string: +// body :"This is the data in the first chunk\r\nand this is the second one\r\n" +// body_size :65 +// header_0 :{ "Content-Type": "text/plain"} +// header_1 :{ "Transfer-Encoding": "chunked"} +// should_keep_alive :1 +// upgrade :0 +// http_major :1 +// http_minor :1 + + +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.StringReader; +import java.io.Reader; +import java.io.Reader; +import java.io.IOException; + +import java.util.*; +import java.util.regex.*; + +import java.nio.ByteBuffer; + +import http_parser.HTTPMethod; +import http_parser.ParserType; + +public class TestLoaderNG { + String fn; + public TestLoaderNG(String filename) { + this.fn = filename; + } + static void p(Object o) { + System.out.println(o); + } + public static List load (String fn) { + List list = null; + try { + BufferedReader buf = new BufferedReader(new FileReader(fn)); + list = load(buf); + } catch (Throwable t) { + throw new RuntimeException(t); + } + return list; + + } + public static Message parse (String message) { + List list = load(new BufferedReader(new StringReader(message))); + if (null == list || 0 == list.size() ) { + return null; + } + return list.get(0); + } + + public static List load (BufferedReader buf) { + List list = new LinkedList(); + String line = null; + Message curr = new Message(); + Pattern pattern = Pattern.compile("(\\S+)\\s*:(.*)"); + try { + while (null != (line = buf.readLine()) ){ + if ("".equals(line.trim())) { + list.add (curr); + curr = new Message(); + continue; + } + Matcher m = pattern.matcher(line); + if (m.matches()) { + // you can not be fucking serious!? + // this has got to be the most retarded regex + // interface in the history of the world ... + // (though I'm sure there's worse c++ regexp libs...) + MatchResult r = m.toMatchResult(); + String key = r.group(1).trim(); + String value = r.group(2).trim(); + if ("name".equals(key)) {curr.name = value;} + else if ("raw".equals(key)) {curr.raw = toByteArray(value);} //! + else if ("type".equals(key)) {curr.type = ParserType.parse(value);} + else if ("method".equals(key)) {curr.method = HTTPMethod.parse(value);} + else if ("status_code".equals(key)) {curr.status_code = Integer.parseInt(value);} + else if ("request_path".equals(key)) {curr.request_path = value;} + else if ("request_url".equals(key)) {curr.request_url = value;} + + else if ("fragment".equals(key)) {curr.fragment = value;} + else if ("query_string".equals(key)) {curr.query_string = value;} + else if ("body".equals(key)) {curr.body = toByteArray(value);} //! + else if ("body_size".equals(key)) {curr.body_size = Integer.parseInt(value);} + else if (key.startsWith("header")) { + String [] h = getHeader(value); + curr.header.put(h[0], h[1]); + } + else if ("should_keep_alive".equals(key)) + {curr.should_keep_alive = (1 == Integer.parseInt(value));} + else if ("upgrade".equals(key)) { curr.upgrade = toByteArray(value);} + else if ("http_major".equals(key)) {curr.http_major = Integer.parseInt(value);} + else if ("http_minor".equals(key)) {curr.http_minor = Integer.parseInt(value);} + } else { + p("WTF?"+line); + } + + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + return list; + } + + static String [] getHeader(String value) { + // { "Host": "0.0.0.0=5000"} + Pattern p = Pattern.compile("\\{ ?\"([^\"]*)\": ?\"(.*)\"}"); + Matcher m = p.matcher(value); + if (!m.matches()) { + p(value); + throw new RuntimeException("something wrong"); + } + String [] result = new String[2]; + MatchResult r = m.toMatchResult(); + result[0] = r.group(1).trim(); + result[1] = r.group(2); //.trim(); + return result; + } + + static final byte BSLASH = 0x5c; + static final byte QUOT = 0x22; + static final byte CR = 0x0d; + static final byte LF = 0x0a; + static final byte n = 0x6e; + static final byte r = 0x72; + + static final Byte[] JAVA_GENERICS_ROCK_HARD = new Byte[0]; + + + static byte [] toByteArray (String quotedString) { + ArrayList bytes = new ArrayList(); + String s = quotedString.substring(1, quotedString.length()-1); + byte [] byts = s.getBytes(java.nio.charset.Charset.forName("UTF8")); + boolean escaped = false; + for (byte b : byts) { + switch (b) { + case BSLASH: + escaped = true; + break; + case n: + if (escaped) { + bytes.add(LF); + escaped = false; + } else { + bytes.add(b); + } + break; + case r: + if (escaped) { + escaped = false; + bytes.add(CR); + } else { + bytes.add(b); + } + break; + case QUOT: + escaped = false; + bytes.add(QUOT); + break; + default: + bytes.add(b); + } + + } + + byts = new byte[bytes.size()]; + int i = 0; + for (Byte b : bytes) { + byts[i++]=b; + } + return byts; + } + + public static void main(String [] args) throws Throwable { + //TestLoaderNG l = new TestLoaderNG(args[0]); + List ts = load(args[0]); + for (Message t : ts) { +// for (int i =0; i!= t.raw.length; ++i) { +// p(i+":"+t.raw[i]); +// } +// try { + t.execute_permutations(); +// } catch (Throwable th) { +// p("failed: "+t.name); +// } + t.execute(); + // System.exit(0); + } + } + + class Header { + String field; + String value; + } + enum LastHeader { + NONE + ,FIELD + ,VALUE + } + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java new file mode 100644 index 0000000..13d8ea0 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java @@ -0,0 +1,62 @@ +package http_parser.lolevel; + +import java.nio.*; + +import static http_parser.lolevel.Util.*; + +public class TestNoOverflowLongBody { + + public static void test (http_parser.ParserType type, int len) { + HTTPParser parser = new HTTPParser(type); + ByteBuffer buf = getBytes(type, len); + + int buflen = buf.limit(); + + parser.execute(Util.SETTINGS_NULL, buf); + + check(buflen == buf.position()); + + buf = buffer("a"); + buflen = buf.limit(); + + for (int i = 0; i!= len; ++i) { + parser.execute(Util.SETTINGS_NULL, buf); + check(buflen == buf.position()); + buf.rewind(); + } + + buf = getBytes(type, len); + buflen = buf.limit(); + + parser.execute(Util.SETTINGS_NULL, buf); + + check(buflen == buf.position()); + + } + + static ByteBuffer getBytes (http_parser.ParserType type, int length) { + if (http_parser.ParserType.HTTP_BOTH == type) { + throw new RuntimeException("only HTTP_REQUEST and HTTP_RESPONSE"); + } + + String template = "%s\r\nConnection: Keep-Alive\r\nContent-Length: %d\r\n\r\n"; + String str = null; + if (http_parser.ParserType.HTTP_REQUEST == type) { + str = String.format(template, "GET / HTTP/1.1", length); + } else { + str = String.format(template, "HTTP/1.0 200 OK", length); + } + return buffer(str); + } + + public static void test () { + p(TestNoOverflowLongBody.class); + test(http_parser.ParserType.HTTP_REQUEST, 1000); + test(http_parser.ParserType.HTTP_REQUEST, 100000); + test(http_parser.ParserType.HTTP_RESPONSE, 1000); + test(http_parser.ParserType.HTTP_RESPONSE, 100000); + } + + + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java new file mode 100644 index 0000000..4159980 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java @@ -0,0 +1,117 @@ +package http_parser.lolevel; + +import java.nio.ByteBuffer; +import http_parser.HTTPException; +import http_parser.Util; + +public class UnitTest { + + static void p(Object o) {System.out.println(o);} + + public static void testErrorFormat() { + String bla = "This has an error in position 10 (the n in 'an')"; + ByteBuffer buf = ByteBuffer.wrap(bla.getBytes()); + buf.position(10); + + String mes = +"This has an error in position 10 (the n in 'an')\n" + +"..........^"; + + check_equals(mes, Util.error ("test error", buf, 0)); + + + bla = "123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J"; + buf = ByteBuffer.wrap(bla.getBytes()); + buf.position(50); + mes = +"56789B123456789C123456789D123456789E123456789F123456789G123456789H123456\n"+ +"....................................^"; + check_equals(mes, Util.error("test trim right and left", buf, 0)); + + + buf.position(5); + mes = +"123456789A123456789B123456789C123456789D123456789E123456789F123456789G12\n"+ +".....^"; + check_equals(mes, Util.error("test trim right", buf, 0)); + + + int limit = buf.limit(); + buf.limit(10); + mes = +"123456789A\n"+ +".....^"; + check_equals(mes, Util.error("all before, not enough after", buf, 0)); + + + + buf.limit(limit); + buf.position(90); + mes = +"9C123456789D123456789E123456789F123456789G123456789H123456789I123456789J\n"+ +"..............................................................^"; + check_equals(mes, Util.error("test trim left", buf, 10)); + } + + + // Test that the error callbacks are properly called. + public static void testErrorCallback () { + String nothttp = "THis is certainly not valid HTTP"; + ByteBuffer buf = ByteBuffer.wrap(nothttp.getBytes()); + + ParserSettings s = new ParserSettings(); + s.on_error = new HTTPErrorCallback() { + public void cb (HTTPParser p, String mes, ByteBuffer buf, int pos) { + throw new HTTPException(mes); + } + }; // err callback + + + HTTPParser p = new HTTPParser(); + try { + p.execute(s, buf); + } catch (HTTPException e) { + check_equals("Invalid HTTP method", e.getMessage()); + } + + buf = ByteBuffer.wrap("GET / HTTP 1.10000".getBytes()); + p = new HTTPParser(); + try { + p.execute(s, buf); + } catch (HTTPException e) { + check_equals("ridiculous http minor", e.getMessage()); + } + + // if no error handler is defined, behave just like the above... + ParserSettings s0 = new ParserSettings(); + + buf = ByteBuffer.wrap("THis is certainly not valid HTTP".getBytes()); + p = new HTTPParser(); + try { + p.execute(s0, buf); + } catch (HTTPException e) { + check_equals("Invalid HTTP method", e.getMessage()); + } + + buf = ByteBuffer.wrap("GET / HTTP 1.10000".getBytes()); + p = new HTTPParser(); + try { + p.execute(s0, buf); + } catch (HTTPException e) { + check_equals("ridiculous http minor", e.getMessage()); + } + } + + static void check_equals(Object supposed2be, Object is) { + if (!supposed2be.equals(is)) { + throw new RuntimeException(is + " is supposed to be "+supposed2be); + } + } + + + public static void test () { + p(UnitTest.class); + testErrorFormat(); + testErrorCallback(); + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java new file mode 100644 index 0000000..9af3d4a --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java @@ -0,0 +1,27 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.util.*; + +import http_parser.ParserType; + +import static http_parser.lolevel.Util.*; + +public class Upgrade { + static final String upgrade = "GET /demo HTTP/1.1\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: WebSocket\r\n\r\n" + + "third key data"; + static void test () { + p(Upgrade.class); + HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST); + ByteBuffer buf = buffer(upgrade); + + int read = parser.execute(Util.SETTINGS_NULL, buf); + check (63 == read); + String s = str(buf); + check ("third key data".equals(str(buf))); + + } + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java new file mode 100644 index 0000000..35469d1 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java @@ -0,0 +1,127 @@ +package http_parser.lolevel; + +import http_parser.FieldData; +import http_parser.HTTPParserUrl; + +import static http_parser.HTTPParserUrl.*; +import static http_parser.lolevel.HTTPParser.*; + +/** + */ +public class Url { + + public static Url[] URL_TESTS = new Url[]{ + new Url("proxy request", "http://hostname/", false, + new HTTPParserUrl( + (1 << UrlFields.UF_SCHEMA.getIndex()) | (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PATH.getIndex()), + 0, + new FieldData[]{ + new FieldData(0,4), + new FieldData(7,8), + new FieldData(0,0), + new FieldData(15,1), + new FieldData(0,0), + new FieldData(0,0) + }), + 0), + new Url("CONNECT request", "hostname:443", true, + new HTTPParserUrl( + (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PORT.getIndex()), + 443, + new FieldData[]{ + new FieldData(0,0), + new FieldData(0,8), + new FieldData(9,3), + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0) + }), + 0), + new Url("proxy ipv6 request", "http://[1:2::3:4]/", false, + new HTTPParserUrl( + (1 << UrlFields.UF_SCHEMA.getIndex()) | (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PATH.getIndex()), + 0, + new FieldData[]{ + new FieldData(0,4), + new FieldData(8,8), + new FieldData(0,0), + new FieldData(17,1), + new FieldData(0,0), + new FieldData(0,0) + }), + 0), + new Url("CONNECT ipv6 address", "[1:2::3:4]:443", true, + new HTTPParserUrl( + (1 << UrlFields.UF_HOST.getIndex()) | (1 << UrlFields.UF_PORT.getIndex()), + 443, + new FieldData[]{ + new FieldData(0,0), + new FieldData(1,8), + new FieldData(11,3), + new FieldData(0,0), + new FieldData(0,0), + new FieldData(0,0) + }), + 0), + new Url("extra ? in query string", + "http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css", + false, + new HTTPParserUrl( + (1 << UrlFields.UF_SCHEMA.getIndex()) | + (1 << UrlFields.UF_HOST.getIndex()) | + (1 << UrlFields.UF_PATH.getIndex()) | + (1 << UrlFields.UF_QUERY.getIndex()), + 0, + new FieldData[]{ + new FieldData(0,4), + new FieldData(7,10), + new FieldData(0,0), + new FieldData(17,12), + new FieldData(30,187), + new FieldData(0,0) + }), + 0), + new Url("proxy empty host", + "http://:443/", + false, + null, + 1), + new Url("proxy empty port", + "http://hostname:/", + false, + null, + 1), + new Url("CONNECT empty host", + ":443", + true, + null, + 1), + new Url("CONNECT empty port", + "hostname:", + true, + null, + 1), + new Url("CONNECT with extra bits", + "hostname:443/", + true, + null, + 1), + + }; + + String name; + String url; + boolean is_connect; + HTTPParserUrl u; + int rv; + + public Url(String name, String url, boolean is_connect, HTTPParserUrl u, int rv) { + this.name = name; + this.url = url; + this.is_connect = is_connect; + this.u = u; + this.rv = rv; + } + + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java new file mode 100644 index 0000000..c73d9e6 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java @@ -0,0 +1,236 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.util.*; + +import primitive.collection.ByteList; + +import http_parser.*; + +public class Util { + + static final ParserSettings SETTINGS_NULL = new ParserSettings(); + + static String str (ByteBuffer b, int pos, int len) { + byte [] by = new byte[len]; + int saved = b.position(); + b.position(pos); + b.get(by); + b.position(saved); + return new String(by); + } + static String str (ByteBuffer b) { + int len = b.limit() - b.position(); + byte [] by = new byte[len]; + int saved = b.position(); + b.get(by); + b.position(saved); + return new String(by); + } + + static ByteBuffer buffer(String str) { + return ByteBuffer.wrap(str.getBytes()); + } + + static ByteBuffer empty() { + return ByteBuffer.wrap(new byte[0]); + } + + static void check(boolean betterBtrue) { + if (!betterBtrue) { + throw new RuntimeException("!"); + } + } + static void check (int should, int is) { + if (should != is) { + throw new RuntimeException("should be: "+should+" is:"+is); + } + } + + static void test_message(Message mes) { + int raw_len = mes.raw.length; + for (int msg1len = 0; msg1len != raw_len; ++msg1len) { + mes.reset(); + ByteBuffer msg1 = ByteBuffer.wrap(mes.raw, 0, msg1len); + ByteBuffer msg2 = ByteBuffer.wrap(mes.raw, msg1len, mes.raw.length - msg1len); + + HTTPParser parser = new HTTPParser(mes.type); + ParserSettings settings = mes.settings(); + + int read = 0; + if (msg1len !=0) { + read = parser.execute(settings, msg1); + if (mes.upgrade() && parser.upgrade) { + // Messages have a settings() that checks itself... + check(1 == mes.num_called); + continue; + } + check(read == msg1len); + } + + read = parser.execute(settings, msg2); + if (mes.upgrade() && parser.upgrade) { + check(1 == mes.num_called); + continue; + } + + check( mes.raw.length - msg1len, read); + + ByteBuffer empty = Util.empty(); + read = parser.execute(settings, empty); + + if (mes.upgrade() && parser.upgrade) { + check(1 == mes.num_called); + continue; + } + check(empty.position() == empty.limit()); + check(0 == read); + check(1 == mes.num_called); + + } + } + + static void test_multiple3(Message r1, Message r2, Message r3) { + int message_count = 1; + if (!r1.upgrade()) { + message_count++; + if (!r2.upgrade()) { + message_count++; + } + } + boolean has_upgrade = (message_count < 3 || r3.upgrade()); + + ByteList blist = new ByteList(); + blist.addAll(r1.raw); + blist.addAll(r2.raw); + blist.addAll(r3.raw); + + byte [] raw = blist.toArray(); + ByteBuffer buf = ByteBuffer.wrap(raw); + + Util.Settings settings = Util.settings(); + HTTPParser parser = new HTTPParser(r1.type); + + int read = parser.execute(settings, buf); + if (has_upgrade && parser.upgrade) { + raw = upgrade_message_fix(raw, read, r1,r2,r3); + check(settings.numCalled == message_count); + return; + } + + check(read == raw.length); + + buf = Util.empty(); + read = parser.execute(settings, buf); + if (has_upgrade && parser.upgrade) { + check(settings.numCalled == message_count); + return; + } + + check(0 == read); + check(settings.numCalled == message_count); + } + + /* Given a sequence of bytes and the number of these that we were able to + * parse, verify that upgrade bodies are correct. + */ + static byte [] upgrade_message_fix(byte[] body, int nread, Message... msgs) { + int off = 0; + for (Message m : msgs) { + off += m.raw.length; + if (m.upgrade()) { + off -= m.upgrade.length; + // Original C: + // Check the portion of the response after its specified upgrade + // if (!check_str_eq(m, "upgrade", body + off, body + nread)) { + // abort(); + // } + // to me, this seems to be equivalent to comparing off and nread ... + check (off, nread); + + // Original C: + // Fix up the response so that message_eq() will verify the beginning + // of the upgrade */ + // + // *(body + nread + strlen(m->upgrade)) = '\0'; + // This only shortens body so the strlen check passes. + return new byte[off]; + + } + } + return null; + } +//upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { +// va_list ap; +// size_t i; +// size_t off = 0; +// +// va_start(ap, nmsgs); +// +// for (i = 0; i < nmsgs; i++) { +// struct message *m = va_arg(ap, struct message *); +// +// off += strlen(m->raw); +// +// if (m->upgrade) { +// off -= strlen(m->upgrade); +// +// /* Check the portion of the response after its specified upgrade */ +// if (!check_str_eq(m, "upgrade", body + off, body + nread)) { +// abort(); +// } +// +// /* Fix up the response so that message_eq() will verify the beginning +// * of the upgrade */ +// *(body + nread + strlen(m->upgrade)) = '\0'; +// messages[num_messages -1 ].upgrade = body + nread; +// +// va_end(ap); +// return; +// } +// } +// +// va_end(ap); +// printf("\n\n*** Error: expected a message with upgrade ***\n"); +// +// abort(); +//} + static void p (Object o) { + System.out.println(o); + } + + static Settings settings() { + return new Settings(); + } + static Message find(List list, String name) { + for (Message m : list) { + if (name.equals(m.name)) { + return m; + } + } + return null; + } + + static class Settings extends ParserSettings { + public int numCalled; + public int bodyCount; + Settings() { + this.on_message_complete = new HTTPCallback() { + public int cb (HTTPParser parser) { + numCalled++; + return 0; + } + }; + this.on_body = new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len) { + bodyCount += len; + return 0; + } + }; + } + + int numCalled () { + return this.numCalled; + } + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java new file mode 100644 index 0000000..fc8f081 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java @@ -0,0 +1,59 @@ +package http_parser.lolevel; + +import java.nio.*; +import java.util.*; + +import http_parser.ParserType; + +import static http_parser.lolevel.Util.*; + +public class WrongContentLength { + static final String contentLength = "GET / HTTP/1.0\r\n" + + "Content-Length: 5\r\n" + + "\r\n" + + "hello" + + "hello_again"; + static void test () { + p(WrongContentLength.class); + HTTPParser parser = new HTTPParser(ParserType.HTTP_REQUEST); + ByteBuffer buf = buffer(contentLength); + + Settings settings = new Settings(); + + int read = parser.execute(settings, buf); + check (settings.msg_cmplt_called); + check ("invalid method".equals(settings.err)); + + } + public static void main(String [] args) { + test(); + } + + static class Settings extends ParserSettings { + public int bodyCount; + public boolean msg_cmplt_called; + public String err; + Settings () { + this.on_message_complete = new HTTPCallback () { + public int cb (HTTPParser p) { + check (5 == bodyCount); + msg_cmplt_called = true; + return 0; + } + }; + this.on_body = new HTTPDataCallback() { + public int cb (HTTPParser p, ByteBuffer b, int pos, int len) { + bodyCount += len; + check ("hello".equals(str(b, pos, len))); + return 0; + } + }; + this.on_error = new HTTPErrorCallback() { + public void cb (HTTPParser p, String mes, ByteBuffer b, int i) { + err = mes; + } + }; + } + } + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/test.c b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/test.c new file mode 100644 index 0000000..3840747 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/test.c @@ -0,0 +1,3425 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include /* rand */ +#include +#include + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 + +#define MAX_HEADERS 13 +#define MAX_ELEMENT_SIZE 2048 + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +static http_parser *parser; + +struct message { + const char *name; // for debugging purposes + const char *raw; + enum http_parser_type type; + enum http_method method; + int status_code; + char request_path[MAX_ELEMENT_SIZE]; + char request_url[MAX_ELEMENT_SIZE]; + char fragment[MAX_ELEMENT_SIZE]; + char query_string[MAX_ELEMENT_SIZE]; + char body[MAX_ELEMENT_SIZE]; + size_t body_size; + const char *host; + const char *userinfo; + uint16_t port; + int num_headers; + enum { NONE=0, FIELD, VALUE } last_header_element; + char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; + int should_keep_alive; + + const char *upgrade; // upgraded body + + unsigned short http_major; + unsigned short http_minor; + + int message_begin_cb_called; + int headers_complete_cb_called; + int message_complete_cb_called; + int message_complete_on_eof; + int body_is_final; +}; + +static int currently_parsing_eof; + +static struct message messages[5]; +static int num_messages; +static http_parser_settings *current_pause_parser; + +/* * R E Q U E S T S * */ +const struct message requests[] = +#define CURL_GET 0 +{ {.name= "curl get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.1\r\n" + "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= + { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } + , { "Host", "0.0.0.0=5000" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define FIREFOX_GET 1 +, {.name= "firefox get" + ,.type= HTTP_REQUEST + ,.raw= "GET /favicon.ico HTTP/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Keep-Alive: 300\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/favicon.ico" + ,.request_url= "/favicon.ico" + ,.num_headers= 8 + ,.headers= + { { "Host", "0.0.0.0=5000" } + , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } + , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } + , { "Accept-Language", "en-us,en;q=0.5" } + , { "Accept-Encoding", "gzip,deflate" } + , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } + , { "Keep-Alive", "300" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define DUMBFUCK 2 +, {.name= "dumbfuck" + ,.type= HTTP_REQUEST + ,.raw= "GET /dumbfuck HTTP/1.1\r\n" + "aaaaaaaaaaaaa:++++++++++\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/dumbfuck" + ,.request_url= "/dumbfuck" + ,.num_headers= 1 + ,.headers= + { { "aaaaaaaaaaaaa", "++++++++++" } + } + ,.body= "" + } + +#define FRAGMENT_IN_URI 3 +, {.name= "fragment in url" + ,.type= HTTP_REQUEST + ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "page=1" + ,.fragment= "posts-17408" + ,.request_path= "/forums/1/topics/2375" + /* XXX request url does include fragment? */ + ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_NO_HEADERS_NO_BODY 4 +, {.name= "get no headers no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_no_headers_no_body/world" + ,.request_url= "/get_no_headers_no_body/world" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_ONE_HEADER_NO_BODY 5 +, {.name= "get one header no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_one_header_no_body" + ,.request_url= "/get_one_header_no_body" + ,.num_headers= 1 + ,.headers= + { { "Accept" , "*/*" } + } + ,.body= "" + } + +#define GET_FUNKY_CONTENT_LENGTH 6 +, {.name= "get funky content length body hello" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" + "conTENT-Length: 5\r\n" + "\r\n" + "HELLO" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_funky_content_length_body_hello" + ,.request_url= "/get_funky_content_length_body_hello" + ,.num_headers= 1 + ,.headers= + { { "conTENT-Length" , "5" } + } + ,.body= "HELLO" + } + +#define POST_IDENTITY_BODY_WORLD 7 +, {.name= "post identity body world" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" + "Accept: */*\r\n" + "Transfer-Encoding: identity\r\n" + "Content-Length: 5\r\n" + "\r\n" + "World" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "q=search" + ,.fragment= "hey" + ,.request_path= "/post_identity_body_world" + ,.request_url= "/post_identity_body_world?q=search#hey" + ,.num_headers= 3 + ,.headers= + { { "Accept", "*/*" } + , { "Transfer-Encoding", "identity" } + , { "Content-Length", "5" } + } + ,.body= "World" + } + +#define POST_CHUNKED_ALL_YOUR_BASE 8 +, {.name= "post - chunked body: all your base are belong to us" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1e\r\nall your base are belong to us\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/post_chunked_all_your_base" + ,.request_url= "/post_chunked_all_your_base" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding" , "chunked" } + } + ,.body= "all your base are belong to us" + } + +#define TWO_CHUNKS_MULT_ZERO_END 9 +, {.name= "two chunks ; triple zero ending" + ,.type= HTTP_REQUEST + ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "000\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/two_chunks_mult_zero_end" + ,.request_url= "/two_chunks_mult_zero_end" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + } + +#define CHUNKED_W_TRAILING_HEADERS 10 +, {.name= "chunked with trailing headers. blech." + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "0\r\n" + "Vary: *\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_trailing_headers" + ,.request_url= "/chunked_w_trailing_headers" + ,.num_headers= 3 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Vary", "*" } + , { "Content-Type", "text/plain" } + } + ,.body= "hello world" + } + +#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 +, {.name= "with bullshit after the length" + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" + "6; blahblah; blah\r\n world\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_bullshit_after_length" + ,.request_url= "/chunked_w_bullshit_after_length" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + } + +#define WITH_QUOTES 12 +, {.name= "with quotes" + ,.type= HTTP_REQUEST + ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=\"bar\"" + ,.fragment= "" + ,.request_path= "/with_\"stupid\"_quotes" + ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define APACHEBENCH_GET 13 +/* The server receiving this request SHOULD NOT wait for EOF + * to know that content-length == 0. + * How to represent this in a unit test? message_complete_on_eof + * Compare with NO_CONTENT_LENGTH_RESPONSE. + */ +, {.name = "apachebench get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.0\r\n" + "Host: 0.0.0.0:5000\r\n" + "User-Agent: ApacheBench/2.3\r\n" + "Accept: */*\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= { { "Host", "0.0.0.0:5000" } + , { "User-Agent", "ApacheBench/2.3" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define QUERY_URL_WITH_QUESTION_MARK_GET 14 +/* Some clients include '?' characters in query strings. + */ +, {.name = "query url with question mark" + ,.type= HTTP_REQUEST + ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=bar?baz" + ,.fragment= "" + ,.request_path= "/test.cgi" + ,.request_url= "/test.cgi?foo=bar?baz" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define PREFIX_NEWLINE_GET 15 +/* Some clients, especially after a POST in a keep-alive connection, + * will send an extra CRLF before the next request + */ +, {.name = "newline prefix get" + ,.type= HTTP_REQUEST + ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define UPGRADE_REQUEST 16 +, {.name = "upgrade request" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Upgrade" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + +#define CONNECT_REQUEST 17 +, {.name = "connect request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + "some data\r\n" + "and yet even more data" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "0-home0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="some data\r\nand yet even more data" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#define REPORT_REQ 18 +, {.name= "report request" + ,.type= HTTP_REQUEST + ,.raw= "REPORT /test HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_REPORT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define NO_HTTP_VERSION 19 +, {.name= "request with no http version" + ,.type= HTTP_REQUEST + ,.raw= "GET /\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 0 + ,.http_minor= 9 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define MSEARCH_REQ 20 +, {.name= "m-search request" + ,.type= HTTP_REQUEST + ,.raw= "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "ST: \"ssdp:all\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_MSEARCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "*" + ,.request_url= "*" + ,.num_headers= 3 + ,.headers= { { "HOST", "239.255.255.250:1900" } + , { "MAN", "\"ssdp:discover\"" } + , { "ST", "\"ssdp:all\"" } + } + ,.body= "" + } + +#define LINE_FOLDING_IN_HEADER 21 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\r\n" + "Line1: abc\r\n" + "\tdef\r\n" + " ghi\r\n" + "\t\tjkl\r\n" + " mno \r\n" + "\t \tqrs\r\n" + "Line2: \t line2\t\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 2 + ,.headers= { { "Line1", "abcdefghijklmno qrs" } + , { "Line2", "line2\t" } + } + ,.body= "" + } + + +#define QUERY_TERMINATED_HOST 22 +, {.name= "host terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org?hail=all" + ,.host= "hypnotoad.org" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define QUERY_TERMINATED_HOSTPORT 23 +, {.name= "host:port terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234?hail=all" + ,.host= "hypnotoad.org" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define SPACE_TERMINATED_HOSTPORT 24 +, {.name= "host:port terminated by a space" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234" + ,.host= "hypnotoad.org" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define PATCH_REQ 25 +, {.name = "PATCH request" + ,.type= HTTP_REQUEST + ,.raw= "PATCH /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/example\r\n" + "If-Match: \"e0023aa4e\"\r\n" + "Content-Length: 10\r\n" + "\r\n" + "cccccccccc" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PATCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 4 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/example" } + , { "If-Match", "\"e0023aa4e\"" } + , { "Content-Length", "10" } + } + ,.body= "cccccccccc" + } + +#define CONNECT_CAPS_REQUEST 26 +, {.name = "connect caps request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "HOME0.NETSCAPE.COM:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#if !HTTP_PARSER_STRICT +#define UTF8_PATH_REQ 27 +, {.name= "utf-8 path request" + ,.type= HTTP_REQUEST + ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" + "Host: github.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "q=1" + ,.fragment= "narf" + ,.request_path= "/δ¶/δt/pope" + ,.request_url= "/δ¶/δt/pope?q=1#narf" + ,.num_headers= 1 + ,.headers= { {"Host", "github.com" } + } + ,.body= "" + } + +#define HOSTNAME_UNDERSCORE 28 +, {.name = "hostname underscore" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "home_0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } +#endif /* !HTTP_PARSER_STRICT */ + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 +, {.name = "eat CRLF between requests, no \"Connection: close\" header" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 3 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + } + ,.body= "q=42" + } + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 +, {.name = "eat CRLF between requests even if \"Connection: close\" is set" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "Connection: close\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 4 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + , { "Connection", "close" } + } + ,.body= "q=42" + } + +#define PURGE_REQ 31 +, {.name = "PURGE request" + ,.type= HTTP_REQUEST + ,.raw= "PURGE /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PURGE + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 1 + ,.headers= { { "Host", "www.example.com" } } + ,.body= "" + } + +#define SEARCH_REQ 32 +, {.name = "SEARCH request" + ,.type= HTTP_REQUEST + ,.raw= "SEARCH / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_SEARCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 1 + ,.headers= { { "Host", "www.example.com" } } + ,.body= "" + } + +#define PROXY_WITH_BASIC_AUTH 33 +, {.name= "host:port and basic_auth" + ,.type= HTTP_REQUEST + ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.fragment= "" + ,.request_path= "/toto" + ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" + ,.host= "hypnotoad.org" + ,.userinfo= "a%12:b!&*$" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + + +, {.name= NULL } /* sentinel */ +}; + +/* * R E S P O N S E S * */ +const struct message responses[] = +#define GOOGLE_301 0 +{ {.name= "google 301" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" + "Location: http://www.google.com/\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" + "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" + "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ + "Cache-Control: public, max-age=2592000\r\n" + "Server: gws\r\n" + "Content-Length: 219 \r\n" + "\r\n" + "\n" + "301 Moved\n" + "

301 Moved

\n" + "The document has moved\n" + "here.\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.num_headers= 8 + ,.headers= + { { "Location", "http://www.google.com/" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } + , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } + , { "X-$PrototypeBI-Version", "1.6.0.3" } + , { "Cache-Control", "public, max-age=2592000" } + , { "Server", "gws" } + , { "Content-Length", "219 " } + } + ,.body= "\n" + "301 Moved\n" + "

301 Moved

\n" + "The document has moved\n" + "here.\r\n" + "\r\n" + } + +#define NO_CONTENT_LENGTH_RESPONSE 1 +/* The client should wait for the server's EOF. That is, when content-length + * is not specified, and "Connection: close", the end of body is specified + * by the EOF. + * Compare with APACHEBENCH_GET + */ +, {.name= "no content-length response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" + "Server: Apache\r\n" + "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" + "Content-Type: text/xml; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n" + "\n" + "\n" + " \n" + " \n" + " SOAP-ENV:Client\n" + " Client Error\n" + " \n" + " \n" + "" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 5 + ,.headers= + { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } + , { "Server", "Apache" } + , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } + , { "Content-Type", "text/xml; charset=utf-8" } + , { "Connection", "close" } + } + ,.body= "\n" + "\n" + " \n" + " \n" + " SOAP-ENV:Client\n" + " Client Error\n" + " \n" + " \n" + "" + } + +#define NO_HEADERS_NO_BODY_404 2 +, {.name= "404 no headers no body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 404 + ,.num_headers= 0 + ,.headers= {} + ,.body_size= 0 + ,.body= "" + } + +#define NO_REASON_PHRASE 3 +, {.name= "301 no response phrase" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301\r\n\r\n" + ,.should_keep_alive = FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define TRAILING_SPACE_ON_CHUNKED_BODY 4 +, {.name="200 trailing space on chunked body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "25 \r\n" + "This is the data in the first chunk\r\n" + "\r\n" + "1C\r\n" + "and this is the second one\r\n" + "\r\n" + "0 \r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/plain" } + , {"Transfer-Encoding", "chunked" } + } + ,.body_size = 37+28 + ,.body = + "This is the data in the first chunk\r\n" + "and this is the second one\r\n" + + } + +#define NO_CARRIAGE_RET 5 +, {.name="no carriage ret" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\n" + "Content-Type: text/html; charset=utf-8\n" + "Connection: close\n" + "\n" + "these headers are from http://news.ycombinator.com/" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/html; charset=utf-8" } + , {"Connection", "close" } + } + ,.body= "these headers are from http://news.ycombinator.com/" + } + +#define PROXY_CONNECTION 6 +, {.name="proxy connection" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Content-Length: 11\r\n" + "Proxy-Connection: close\r\n" + "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 4 + ,.headers= + { {"Content-Type", "text/html; charset=UTF-8" } + , {"Content-Length", "11" } + , {"Proxy-Connection", "close" } + , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} + } + ,.body= "hello world" + } + +#define UNDERSTORE_HEADER_KEY 7 + // shown by + // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" +, {.name="underscore header key" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: DCLK-AdSvr\r\n" + "Content-Type: text/xml\r\n" + "Content-Length: 0\r\n" + "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 4 + ,.headers= + { {"Server", "DCLK-AdSvr" } + , {"Content-Type", "text/xml" } + , {"Content-Length", "0" } + , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } + } + ,.body= "" + } + +#define BONJOUR_MADAME_FR 8 +/* The client should not merge two headers fields when the first one doesn't + * have a value. + */ +, {.name= "bonjourmadame.fr" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" + "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" + "Server: Apache/2.2.3 (Red Hat)\r\n" + "Cache-Control: public\r\n" + "Pragma: \r\n" + "Location: http://www.bonjourmadame.fr/\r\n" + "Vary: Accept-Encoding\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 301 + ,.num_headers= 9 + ,.headers= + { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } + , { "Server", "Apache/2.2.3 (Red Hat)" } + , { "Cache-Control", "public" } + , { "Pragma", "" } + , { "Location", "http://www.bonjourmadame.fr/" } + , { "Vary", "Accept-Encoding" } + , { "Content-Length", "0" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define RES_FIELD_UNDERSCORE 9 +/* Should handle spaces in header fields */ +, {.name= "field underscore" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" + "Server: Apache\r\n" + "Cache-Control: no-cache, must-revalidate\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" + "Vary: Accept-Encoding\r\n" + "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ + "_onnection: Keep-Alive\r\n" /* semantic value ignored */ + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "0\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 11 + ,.headers= + { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } + , { "Server", "Apache" } + , { "Cache-Control", "no-cache, must-revalidate" } + , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } + , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } + , { "Vary", "Accept-Encoding" } + , { "_eep-Alive", "timeout=45" } + , { "_onnection", "Keep-Alive" } + , { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/html" } + , { "Connection", "close" } + } + ,.body= "" + } + +#define NON_ASCII_IN_STATUS_LINE 10 +/* Should handle non-ASCII in status line */ +, {.name= "non-ASCII in status line" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" + "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 500 + ,.num_headers= 3 + ,.headers= + { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } + , { "Content-Length", "0" } + , { "Connection", "close" } + } + ,.body= "" + } + +#define HTTP_VERSION_0_9 11 +/* Should handle HTTP/0.9 */ +, {.name= "http version 0.9" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/0.9 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 0 + ,.http_minor= 9 + ,.status_code= 200 + ,.num_headers= 0 + ,.headers= + {} + ,.body= "" + } + +#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 +/* The client should wait for the server's EOF. That is, when neither + * content-length nor transfer-encoding is specified, the end of body + * is specified by the EOF. + */ +, {.name= "neither content-length nor transfer-encoding response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Content-Type", "text/plain" } + } + ,.body= "hello world" + } + +#define NO_BODY_HTTP10_KA_200 13 +, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 200 OK\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP10_KA_204 14 +, {.name= "HTTP/1.0 with keep-alive and a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 204 No content\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 204 + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_200 15 +, {.name= "HTTP/1.1 with an EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_204 16 +, {.name= "HTTP/1.1 with a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_NOKA_204 17 +, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.num_headers= 1 + ,.headers= + { { "Connection", "close" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_CHUNKED_200 18 +, {.name= "HTTP/1.1 with chunked endocing and a 200 response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body_size= 0 + ,.body= "" + } + +#if !HTTP_PARSER_STRICT +#define SPACE_IN_FIELD_RES 19 +/* Should handle spaces in header fields */ +, {.name= "field space" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: Microsoft-IIS/6.0\r\n" + "X-Powered-By: ASP.NET\r\n" + "en-US Content-Type: text/xml\r\n" /* this is the problem */ + "Content-Type: text/xml\r\n" + "Content-Length: 16\r\n" + "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" + "Connection: keep-alive\r\n" + "\r\n" + "hello" /* fake body */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 7 + ,.headers= + { { "Server", "Microsoft-IIS/6.0" } + , { "X-Powered-By", "ASP.NET" } + , { "en-US Content-Type", "text/xml" } + , { "Content-Type", "text/xml" } + , { "Content-Length", "16" } + , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } + , { "Connection", "keep-alive" } + } + ,.body= "hello" + } +#endif /* !HTTP_PARSER_STRICT */ + +, {.name= NULL } /* sentinel */ +}; + +/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so + * define it ourselves. + */ +size_t +strnlen(const char *s, size_t maxlen) +{ + const char *p; + + p = memchr(s, '\0', maxlen); + if (p == NULL) + return maxlen; + + return p - s; +} + +size_t +strlncat(char *dst, size_t len, const char *src, size_t n) +{ + size_t slen; + size_t dlen; + size_t rlen; + size_t ncpy; + + slen = strnlen(src, n); + dlen = strnlen(dst, len); + + if (dlen < len) { + rlen = len - dlen; + ncpy = slen < rlen ? slen : (rlen - 1); + memcpy(dst + dlen, src, ncpy); + dst[dlen + ncpy] = '\0'; + } + + assert(len > slen + dlen); + return slen + dlen; +} + +size_t +strlcat(char *dst, const char *src, size_t len) +{ + return strlncat(dst, len, src, (size_t) -1); +} + +size_t +strlncpy(char *dst, size_t len, const char *src, size_t n) +{ + size_t slen; + size_t ncpy; + + slen = strnlen(src, n); + + if (len > 0) { + ncpy = slen < len ? slen : (len - 1); + memcpy(dst, src, ncpy); + dst[ncpy] = '\0'; + } + + assert(len > slen); + return slen; +} + +size_t +strlcpy(char *dst, const char *src, size_t len) +{ + return strlncpy(dst, len, src, (size_t) -1); +} + +int +request_url_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strlncat(messages[num_messages].request_url, + sizeof(messages[num_messages].request_url), + buf, + len); + return 0; +} + +int +status_complete_cb (http_parser *p) { + assert(p == parser); + p->data++; + return 0; +} + +int +header_field_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + if (m->last_header_element != FIELD) + m->num_headers++; + + strlncat(m->headers[m->num_headers-1][0], + sizeof(m->headers[m->num_headers-1][0]), + buf, + len); + + m->last_header_element = FIELD; + + return 0; +} + +int +header_value_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + strlncat(m->headers[m->num_headers-1][1], + sizeof(m->headers[m->num_headers-1][1]), + buf, + len); + + m->last_header_element = VALUE; + + return 0; +} + +void +check_body_is_final (const http_parser *p) +{ + if (messages[num_messages].body_is_final) { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + messages[num_messages].body_is_final = http_body_is_final(p); +} + +int +body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strlncat(messages[num_messages].body, + sizeof(messages[num_messages].body), + buf, + len); + messages[num_messages].body_size += len; + check_body_is_final(p); + // printf("body_cb: '%s'\n", requests[num_messages].body); + return 0; +} + +int +count_body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + assert(buf); + messages[num_messages].body_size += len; + check_body_is_final(p); + return 0; +} + +int +message_begin_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].message_begin_cb_called = TRUE; + return 0; +} + +int +headers_complete_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].method = parser->method; + messages[num_messages].status_code = parser->status_code; + messages[num_messages].http_major = parser->http_major; + messages[num_messages].http_minor = parser->http_minor; + messages[num_messages].headers_complete_cb_called = TRUE; + messages[num_messages].should_keep_alive = http_should_keep_alive(parser); + return 0; +} + +int +message_complete_cb (http_parser *p) +{ + assert(p == parser); + if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) + { + fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " + "value in both on_message_complete and on_headers_complete " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + + if (messages[num_messages].body_size && + http_body_is_final(p) && + !messages[num_messages].body_is_final) + { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + + messages[num_messages].message_complete_cb_called = TRUE; + + messages[num_messages].message_complete_on_eof = currently_parsing_eof; + + num_messages++; + return 0; +} + +/* These dontcall_* callbacks exist so that we can verify that when we're + * paused, no additional callbacks are invoked */ +int +dontcall_message_begin_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_body_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_headers_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_headers_complete() called on paused " + "parser ***\n\n"); + abort(); +} + +int +dontcall_message_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_complete() called on paused " + "parser ***\n\n"); + abort(); +} + +static http_parser_settings settings_dontcall = + {.on_message_begin = dontcall_message_begin_cb + ,.on_header_field = dontcall_header_field_cb + ,.on_header_value = dontcall_header_value_cb + ,.on_url = dontcall_request_url_cb + ,.on_body = dontcall_body_cb + ,.on_headers_complete = dontcall_headers_complete_cb + ,.on_message_complete = dontcall_message_complete_cb + }; + +/* These pause_* callbacks always pause the parser and just invoke the regular + * callback that tracks content. Before returning, we overwrite the parser + * settings to point to the _dontcall variety so that we can verify that + * the pause actually did, you know, pause. */ +int +pause_message_begin_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_begin_cb(p); +} + +int +pause_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_field_cb(p, buf, len); +} + +int +pause_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_value_cb(p, buf, len); +} + +int +pause_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return request_url_cb(p, buf, len); +} + +int +pause_body_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return body_cb(p, buf, len); +} + +int +pause_headers_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return headers_complete_cb(p); +} + +int +pause_message_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_complete_cb(p); +} + +static http_parser_settings settings_pause = + {.on_message_begin = pause_message_begin_cb + ,.on_header_field = pause_header_field_cb + ,.on_header_value = pause_header_value_cb + ,.on_url = pause_request_url_cb + ,.on_body = pause_body_cb + ,.on_headers_complete = pause_headers_complete_cb + ,.on_message_complete = pause_message_complete_cb + }; + +static http_parser_settings settings = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_body = body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + }; + +static http_parser_settings settings_count_body = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_body = count_body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + }; + +static http_parser_settings settings_null = + {.on_message_begin = 0 + ,.on_header_field = 0 + ,.on_header_value = 0 + ,.on_url = 0 + ,.on_body = 0 + ,.on_headers_complete = 0 + ,.on_message_complete = 0 + }; + +void +parser_init (enum http_parser_type type) +{ + num_messages = 0; + + assert(parser == NULL); + + parser = malloc(sizeof(http_parser)); + + http_parser_init(parser, type); + + memset(&messages, 0, sizeof messages); + +} + +void +parser_free () +{ + assert(parser); + free(parser); + parser = NULL; +} + +size_t parse (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings, buf, len); + return nparsed; +} + +size_t parse_count_body (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings_count_body, buf, len); + return nparsed; +} + +size_t parse_pause (const char *buf, size_t len) +{ + size_t nparsed; + http_parser_settings s = settings_pause; + + currently_parsing_eof = (len == 0); + current_pause_parser = &s; + nparsed = http_parser_execute(parser, current_pause_parser, buf, len); + return nparsed; +} + +static inline int +check_str_eq (const struct message *m, + const char *prop, + const char *expected, + const char *found) { + if ((expected == NULL) != (found == NULL)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %s\n", (expected == NULL) ? "NULL" : expected); + printf(" found %s\n", (found == NULL) ? "NULL" : found); + return 0; + } + if (expected != NULL && 0 != strcmp(expected, found)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected '%s'\n", expected); + printf(" found '%s'\n", found); + return 0; + } + return 1; +} + +static inline int +check_num_eq (const struct message *m, + const char *prop, + int expected, + int found) { + if (expected != found) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %d\n", expected); + printf(" found %d\n", found); + return 0; + } + return 1; +} + +#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ + if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ + if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ +do { \ + char ubuf[256]; \ + \ + if ((u)->field_set & (1 << (fn))) { \ + memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ + (u)->field_data[(fn)].len); \ + ubuf[(u)->field_data[(fn)].len] = '\0'; \ + } else { \ + ubuf[0] = '\0'; \ + } \ + \ + check_str_eq(expected, #prop, expected->prop, ubuf); \ +} while(0) + +int +message_eq (int index, const struct message *expected) +{ + int i; + struct message *m = &messages[index]; + + MESSAGE_CHECK_NUM_EQ(expected, m, http_major); + MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); + + if (expected->type == HTTP_REQUEST) { + MESSAGE_CHECK_NUM_EQ(expected, m, method); + } else { + MESSAGE_CHECK_NUM_EQ(expected, m, status_code); + } + + MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); + MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); + + assert(m->message_begin_cb_called); + assert(m->headers_complete_cb_called); + assert(m->message_complete_cb_called); + + + MESSAGE_CHECK_STR_EQ(expected, m, request_url); + + /* Check URL components; we can't do this w/ CONNECT since it doesn't + * send us a well-formed URL. + */ + if (*m->request_url && m->method != HTTP_CONNECT) { + struct http_parser_url u; + + if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { + fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", + m->request_url); + abort(); + } + + if (expected->host) { + MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); + } + + if (expected->userinfo) { + MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); + } + + m->port = (u.field_set & (1 << UF_PORT)) ? + u.port : 0; + + MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); + MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); + MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); + MESSAGE_CHECK_NUM_EQ(expected, m, port); + } + + if (expected->body_size) { + MESSAGE_CHECK_NUM_EQ(expected, m, body_size); + } else { + MESSAGE_CHECK_STR_EQ(expected, m, body); + } + + MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); + + int r; + for (i = 0; i < m->num_headers; i++) { + r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); + if (!r) return 0; + r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); + if (!r) return 0; + } + + MESSAGE_CHECK_STR_EQ(expected, m, upgrade); + + return 1; +} + +/* Given a sequence of varargs messages, return the number of them that the + * parser should successfully parse, taking into account that upgraded + * messages prevent all subsequent messages from being parsed. + */ +size_t +count_parsed_messages(const size_t nmsgs, ...) { + size_t i; + va_list ap; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + if (m->upgrade) { + va_end(ap); + return i + 1; + } + } + + va_end(ap); + return nmsgs; +} + +/* Given a sequence of bytes and the number of these that we were able to + * parse, verify that upgrade bodies are correct. + */ +void +upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { + va_list ap; + size_t i; + size_t off = 0; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + off += strlen(m->raw); + + if (m->upgrade) { + off -= strlen(m->upgrade); + + /* Check the portion of the response after its specified upgrade */ + if (!check_str_eq(m, "upgrade", body + off, body + nread)) { + abort(); + } + + /* Fix up the response so that message_eq() will verify the beginning + * of the upgrade */ + *(body + nread + strlen(m->upgrade)) = '\0'; + messages[num_messages -1 ].upgrade = body + nread; + + va_end(ap); + return; + } + } + + va_end(ap); + printf("\n\n*** Error: expected a message with upgrade ***\n"); + + abort(); +} + +static void +print_error (const char *raw, size_t error_location) +{ + fprintf(stderr, "\n*** %s ***\n\n", + http_errno_description(HTTP_PARSER_ERRNO(parser))); + + int this_line = 0, char_len = 0; + size_t i, j, len = strlen(raw), error_location_line = 0; + for (i = 0; i < len; i++) { + if (i == error_location) this_line = 1; + switch (raw[i]) { + case '\r': + char_len = 2; + fprintf(stderr, "\\r"); + break; + + case '\n': + char_len = 2; + fprintf(stderr, "\\n\n"); + + if (this_line) goto print; + + error_location_line = 0; + continue; + + default: + char_len = 1; + fputc(raw[i], stderr); + break; + } + if (!this_line) error_location_line += char_len; + } + + fprintf(stderr, "[eof]\n"); + + print: + for (j = 0; j < error_location_line; j++) { + fputc(' ', stderr); + } + fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); +} + +void +test_preserve_data (void) +{ + char my_data[] = "application-specific data"; + http_parser parser; + parser.data = my_data; + http_parser_init(&parser, HTTP_REQUEST); + if (parser.data != my_data) { + printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); + abort(); + } +} + +struct url_test { + const char *name; + const char *url; + int is_connect; + struct http_parser_url u; + int rv; +}; + +const struct url_test url_tests[] = +{ {.name="proxy request" + ,.url="http://hostname/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 15, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="proxy request with port" + ,.url="http://hostname:444/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) + ,.port=444 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 16, 3 } /* UF_PORT */ + ,{ 19, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT request" + ,.url="hostname:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 8 } /* UF_HOST */ + ,{ 9, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT request but not connect" + ,.url="hostname:443" + ,.is_connect=0 + ,.rv=1 + } + +, {.name="proxy ipv6 request" + ,.url="http://[1:2::3:4]/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 17, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="proxy ipv6 request with port" + ,.url="http://[1:2::3:4]:67/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) + ,.port=67 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 18, 2 } /* UF_PORT */ + ,{ 20, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT ipv6 address" + ,.url="[1:2::3:4]:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 1, 8 } /* UF_HOST */ + ,{ 11, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="ipv4 in ipv6 address" + ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 37 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 46, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="extra ? in query string" + ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," + "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," + "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" + ,.is_connect=0 + ,.u= + {.field_set=(1<field_set, u->port); + for (i = 0; i < UF_MAX; i++) { + if ((u->field_set & (1 << i)) == 0) { + printf("\tfield_data[%u]: unset\n", i); + continue; + } + + printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", + i, + u->field_data[i].off, + u->field_data[i].len, + u->field_data[i].len, + url + u->field_data[i].off); + } +} + +void +test_parse_url (void) +{ + struct http_parser_url u; + const struct url_test *test; + unsigned int i; + int rv; + + for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { + test = &url_tests[i]; + memset(&u, 0, sizeof(u)); + + rv = http_parser_parse_url(test->url, + strlen(test->url), + test->is_connect, + &u); + + if (test->rv == 0) { + if (rv != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + abort(); + } + + if (memcmp(&u, &test->u, sizeof(u)) != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", + test->url, test->name); + + printf("target http_parser_url:\n"); + dump_url(test->url, &test->u); + printf("result http_parser_url:\n"); + dump_url(test->url, &u); + + abort(); + } + } else { + /* test->rv != 0 */ + if (rv == 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + abort(); + } + } + } +} + +void +test_method_str (void) +{ + assert(0 == strcmp("GET", http_method_str(HTTP_GET))); + assert(0 == strcmp("", http_method_str(1337))); +} + +void +test_message (const struct message *message) +{ + size_t raw_len = strlen(message->raw); + size_t msg1len; + for (msg1len = 0; msg1len < raw_len; msg1len++) { + parser_init(message->type); + + size_t read; + const char *msg1 = message->raw; + const char *msg2 = msg1 + msg1len; + size_t msg2len = raw_len - msg1len; + + if (msg1len) { + read = parse(msg1, msg1len); + + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg1 + read; + goto test; + } + + if (read != msg1len) { + print_error(msg1, read); + abort(); + } + } + + + read = parse(msg2, msg2len); + + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg2 + read; + goto test; + } + + if (read != msg2len) { + print_error(msg2, read); + abort(); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(message->raw, read); + abort(); + } + + test: + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + abort(); + } + + if(!message_eq(0, message)) abort(); + + parser_free(); + } +} + +void +test_message_count_body (const struct message *message) +{ + parser_init(message->type); + + size_t read; + size_t l = strlen(message->raw); + size_t i, toread; + size_t chunk = 4024; + + for (i = 0; i < l; i+= chunk) { + toread = MIN(l-i, chunk); + read = parse_count_body(message->raw + i, toread); + if (read != toread) { + print_error(message->raw, read); + abort(); + } + } + + + read = parse_count_body(NULL, 0); + if (read != 0) { + print_error(message->raw, read); + abort(); + } + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + abort(); + } + + if(!message_eq(0, message)) abort(); + + parser_free(); +} + +void +test_simple (const char *buf, enum http_errno err_expected) +{ + parser_init(HTTP_REQUEST); + + size_t parsed; + int pass; + enum http_errno err; + + parsed = parse(buf, strlen(buf)); + pass = (parsed == strlen(buf)); + err = HTTP_PARSER_ERRNO(parser); + parsed = parse(NULL, 0); + pass &= (parsed == 0); + + parser_free(); + + /* In strict mode, allow us to pass with an unexpected HPE_STRICT as + * long as the caller isn't expecting success. + */ +#if HTTP_PARSER_STRICT + if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { +#else + if (err_expected != err) { +#endif + fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", + http_errno_name(err_expected), http_errno_name(err), buf); + abort(); + } +} + +void +test_header_overflow_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "header-key: header-value\r\n"; + size_t buflen = strlen(buf); + + int i; + for (i = 0; i < 10000; i++) { + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + //fprintf(stderr, "error found on iter %d\n", i); + assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); + return; + } + } + + fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); + abort(); +} + +static void +test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) +{ + http_parser parser; + http_parser_init(&parser, HTTP_RESPONSE); + http_parser_execute(&parser, &settings_null, buf, buflen); + + if (expect_ok) + assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); + else + assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); +} + +void +test_header_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Length: " #size "\r\n" \ + "\r\n" + const char a[] = X(18446744073709551614); /* 2^64-2 */ + const char b[] = X(18446744073709551615); /* 2^64-1 */ + const char c[] = X(18446744073709551616); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_chunk_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" \ + #size "\r\n" \ + "..." + const char a[] = X(FFFFFFFFFFFFFFFE); /* 2^64-2 */ + const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ + const char c[] = X(10000000000000000); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_no_overflow_long_body (int req, size_t length) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + size_t i; + char buf1[3000]; + size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", + req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) + goto err; + + for (i = 0; i < length; i++) { + char foo = 'a'; + parsed = http_parser_execute(&parser, &settings_null, &foo, 1); + if (parsed != 1) + goto err; + } + + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) goto err; + return; + + err: + fprintf(stderr, + "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", + req ? "REQUEST" : "RESPONSE", + (unsigned long)length); + abort(); +} + +void +test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) +{ + int message_count = count_parsed_messages(3, r1, r2, r3); + + char total[ strlen(r1->raw) + + strlen(r2->raw) + + strlen(r3->raw) + + 1 + ]; + total[0] = '\0'; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + parser_init(r1->type); + + size_t read; + + read = parse(total, strlen(total)); + + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + goto test; + } + + if (read != strlen(total)) { + print_error(total, read); + abort(); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(total, read); + abort(); + } + +test: + + if (message_count != num_messages) { + fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); + abort(); + } + + if (!message_eq(0, r1)) abort(); + if (message_count > 1 && !message_eq(1, r2)) abort(); + if (message_count > 2 && !message_eq(2, r3)) abort(); + + parser_free(); +} + +/* SCAN through every possible breaking to make sure the + * parser can handle getting the content in any chunks that + * might come from the socket + */ +void +test_scan (const struct message *r1, const struct message *r2, const struct message *r3) +{ + char total[80*1024] = "\0"; + char buf1[80*1024] = "\0"; + char buf2[80*1024] = "\0"; + char buf3[80*1024] = "\0"; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + size_t read; + + int total_len = strlen(total); + + int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; + int ops = 0 ; + + size_t buf1_len, buf2_len, buf3_len; + int message_count = count_parsed_messages(3, r1, r2, r3); + + int i,j,type_both; + for (type_both = 0; type_both < 2; type_both ++ ) { + for (j = 2; j < total_len; j ++ ) { + for (i = 1; i < j; i ++ ) { + + if (ops % 1000 == 0) { + printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); + fflush(stdout); + } + ops += 1; + + parser_init(type_both ? HTTP_BOTH : r1->type); + + buf1_len = i; + strlncpy(buf1, sizeof(buf1), total, buf1_len); + buf1[buf1_len] = 0; + + buf2_len = j - i; + strlncpy(buf2, sizeof(buf1), total+i, buf2_len); + buf2[buf2_len] = 0; + + buf3_len = total_len - j; + strlncpy(buf3, sizeof(buf1), total+j, buf3_len); + buf3[buf3_len] = 0; + + read = parse(buf1, buf1_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len) { + print_error(buf1, read); + goto error; + } + + read += parse(buf2, buf2_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len) { + print_error(buf2, read); + goto error; + } + + read += parse(buf3, buf3_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len + buf3_len) { + print_error(buf3, read); + goto error; + } + + parse(NULL, 0); + +test: + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + } + + if (message_count != num_messages) { + fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", + message_count, num_messages); + goto error; + } + + if (!message_eq(0, r1)) { + fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); + goto error; + } + + if (message_count > 1 && !message_eq(1, r2)) { + fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); + goto error; + } + + if (message_count > 2 && !message_eq(2, r3)) { + fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); + goto error; + } + + parser_free(); + } + } + } + puts("\b\b\b\b100%"); + return; + + error: + fprintf(stderr, "i=%d j=%d\n", i, j); + fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); + fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); + fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); + abort(); +} + +// user required to free the result +// string terminated by \0 +char * +create_large_chunked_message (int body_size_in_kb, const char* headers) +{ + int i; + size_t wrote = 0; + size_t headers_len = strlen(headers); + size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; + char * buf = malloc(bufsize); + + memcpy(buf, headers, headers_len); + wrote += headers_len; + + for (i = 0; i < body_size_in_kb; i++) { + // write 1kb chunk into the body. + memcpy(buf + wrote, "400\r\n", 5); + wrote += 5; + memset(buf + wrote, 'C', 1024); + wrote += 1024; + strcpy(buf + wrote, "\r\n"); + wrote += 2; + } + + memcpy(buf + wrote, "0\r\n\r\n", 6); + wrote += 6; + assert(wrote == bufsize); + + return buf; +} + +void +test_status_complete (void) +{ + parser_init(HTTP_RESPONSE); + parser->data = 0; + http_parser_settings settings = settings_null; + settings.on_status_complete = status_complete_cb; + + char *response = "don't mind me, just a simple response"; + http_parser_execute(parser, &settings, response, strlen(response)); + assert(parser->data == (void*)0); // the status_complete callback was never called + assert(parser->http_errno == HPE_INVALID_CONSTANT); // the errno for an invalid status line +} + +/* Verify that we can pause parsing at any of the bytes in the + * message and still get the result that we're expecting. */ +void +test_message_pause (const struct message *msg) +{ + char *buf = (char*) msg->raw; + size_t buflen = strlen(msg->raw); + size_t nread; + + parser_init(msg->type); + + do { + nread = parse_pause(buf, buflen); + + // We can only set the upgrade buffer once we've gotten our message + // completion callback. + if (messages[0].message_complete_cb_called && + msg->upgrade && + parser->upgrade) { + messages[0].upgrade = buf + nread; + goto test; + } + + if (nread < buflen) { + + // Not much do to if we failed a strict-mode check + if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { + parser_free(); + return; + } + + assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); + } + + buf += nread; + buflen -= nread; + http_parser_pause(parser, 0); + } while (buflen > 0); + + nread = parse_pause(NULL, 0); + assert (nread == 0); + +test: + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); + abort(); + } + + if(!message_eq(0, msg)) abort(); + + parser_free(); +} + +int +main (void) +{ + parser = NULL; + int i, j, k; + int request_count; + int response_count; + + printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); + + for (request_count = 0; requests[request_count].name; request_count++); + for (response_count = 0; responses[response_count].name; response_count++); + + //// API + test_preserve_data(); + test_parse_url(); + test_method_str(); + + //// OVERFLOW CONDITIONS + + test_header_overflow_error(HTTP_REQUEST); + test_no_overflow_long_body(HTTP_REQUEST, 1000); + test_no_overflow_long_body(HTTP_REQUEST, 100000); + + test_header_overflow_error(HTTP_RESPONSE); + test_no_overflow_long_body(HTTP_RESPONSE, 1000); + test_no_overflow_long_body(HTTP_RESPONSE, 100000); + + test_header_content_length_overflow_error(); + test_chunk_content_length_overflow_error(); + + //// RESPONSES + + for (i = 0; i < response_count; i++) { + test_message(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + test_message_pause(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + if (!responses[i].should_keep_alive) continue; + for (j = 0; j < response_count; j++) { + if (!responses[j].should_keep_alive) continue; + for (k = 0; k < response_count; k++) { + test_multiple3(&responses[i], &responses[j], &responses[k]); + } + } + } + + test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); + test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); + + // test very large chunked response + { + char * msg = create_large_chunked_message(31337, + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n"); + struct message large_chunked = + {.name= "large chunked" + ,.type= HTTP_RESPONSE + ,.raw= msg + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/plain" } + } + ,.body_size= 31337*1024 + }; + test_message_count_body(&large_chunked); + free(msg); + } + + + + printf("response scan 1/2 "); + test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] + , &responses[NO_BODY_HTTP10_KA_204] + , &responses[NO_REASON_PHRASE] + ); + + printf("response scan 2/2 "); + test_scan( &responses[BONJOUR_MADAME_FR] + , &responses[UNDERSTORE_HEADER_KEY] + , &responses[NO_CARRIAGE_RET] + ); + + puts("responses okay"); + + + /// REQUESTS + + test_simple("hello world", HPE_INVALID_METHOD); + test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); + + + test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + + // Well-formed but incomplete + test_simple("GET / HTTP/1.1\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 6\r\n" + "\r\n" + "fooba", + HPE_OK); + + static const char *all_methods[] = { + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "UNLOCK", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + 0 }; + const char **this_method; + for (this_method = all_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_OK); + } + + static const char *bad_methods[] = { + "C******", + "M****", + 0 }; + for (this_method = bad_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_UNKNOWN); + } + + const char *dumbfuck2 = + "GET / HTTP/1.1\r\n" + "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" + "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" + "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" + "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" + "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" + "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" + "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" + "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" + "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" + "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" + "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" + "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" + "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" + "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" + "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" + "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" + "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" + "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" + "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" + "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" + "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" + "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" + "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" + "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" + "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" + "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" + "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" + "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" + "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" + "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" + "\tRA==\r\n" + "\t-----END CERTIFICATE-----\r\n" + "\r\n"; + test_simple(dumbfuck2, HPE_OK); + +#if 0 + // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body + // until EOF. + // + // no content-length + // error if there is a body without content length + const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + "HELLO"; + test_simple(bad_get_no_headers_no_body, 0); +#endif + /* TODO sending junk and large headers gets rejected */ + + + /* check to make sure our predefined requests are okay */ + for (i = 0; requests[i].name; i++) { + test_message(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + test_message_pause(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + if (!requests[i].should_keep_alive) continue; + for (j = 0; j < request_count; j++) { + if (!requests[j].should_keep_alive) continue; + for (k = 0; k < request_count; k++) { + test_multiple3(&requests[i], &requests[j], &requests[k]); + } + } + } + + printf("request scan 1/4 "); + test_scan( &requests[GET_NO_HEADERS_NO_BODY] + , &requests[GET_ONE_HEADER_NO_BODY] + , &requests[GET_NO_HEADERS_NO_BODY] + ); + + printf("request scan 2/4 "); + test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] + , &requests[POST_IDENTITY_BODY_WORLD] + , &requests[GET_FUNKY_CONTENT_LENGTH] + ); + + printf("request scan 3/4 "); + test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] + , &requests[CHUNKED_W_TRAILING_HEADERS] + , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] + ); + + printf("request scan 4/4 "); + test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] + , &requests[PREFIX_NEWLINE_GET ] + , &requests[CONNECT_REQUEST] + ); + + test_status_complete(); + + puts("requests okay"); + + return 0; +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped new file mode 100644 index 0000000..038bb52 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped @@ -0,0 +1,845 @@ +name :curl get +raw :"GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/test +request_url :/test +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "User-Agent": "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1"} +header_1 :{ "Host": "0.0.0.0=5000"} +header_2 :{ "Accept": "*/*"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :firefox get +raw :"GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/favicon.ico +request_url :/favicon.ico +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Host": "0.0.0.0=5000"} +header_1 :{ "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"} +header_2 :{ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"} +header_3 :{ "Accept-Language": "en-us,en;q=0.5"} +header_4 :{ "Accept-Encoding": "gzip,deflate"} +header_5 :{ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7"} +header_6 :{ "Keep-Alive": "300"} +header_7 :{ "Connection": "keep-alive"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :dumbfuck +raw :"GET /dumbfuck HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/dumbfuck +request_url :/dumbfuck +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "aaaaaaaaaaaaa": "++++++++++"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :fragment in url +raw :"GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/forums/1/topics/2375 +request_url :/forums/1/topics/2375?page=1#posts-17408 +fragment :posts-17408 +query_string:page=1 +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :get no headers no body +raw :"GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/get_no_headers_no_body/world +request_url :/get_no_headers_no_body/world +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :get one header no body +raw :"GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/get_one_header_no_body +request_url :/get_one_header_no_body +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Accept": "*/*"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :get funky content length body hello +raw :"GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/get_funky_content_length_body_hello +request_url :/get_funky_content_length_body_hello +fragment : +query_string: +body :"HELLO" +body_size :0 +header_0 :{ "conTENT-Length": "5"} +should_keep_alive :0 +http_major :1 +http_minor :0 + +name :post identity body world +raw :"POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/post_identity_body_world +request_url :/post_identity_body_world?q=search#hey +fragment :hey +query_string:q=search +body :"World" +body_size :0 +header_0 :{ "Accept": "*/*"} +header_1 :{ "Transfer-Encoding": "identity"} +header_2 :{ "Content-Length": "5"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :post - chunked body: all your base are belong to us +raw :"POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/post_chunked_all_your_base +request_url :/post_chunked_all_your_base +fragment : +query_string: +body :"all your base are belong to us" +body_size :0 +header_0 :{ "Transfer-Encoding": "chunked"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :two chunks ; triple zero ending +raw :"POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/two_chunks_mult_zero_end +request_url :/two_chunks_mult_zero_end +fragment : +query_string: +body :"hello world" +body_size :0 +header_0 :{ "Transfer-Encoding": "chunked"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :chunked with trailing headers. blech. +raw :"POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/chunked_w_trailing_headers +request_url :/chunked_w_trailing_headers +fragment : +query_string: +body :"hello world" +body_size :0 +header_0 :{ "Transfer-Encoding": "chunked"} +header_1 :{ "Vary": "*"} +header_2 :{ "Content-Type": "text/plain"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :with bullshit after the length +raw :"POST /chunked_w_bullshit_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n6; blahblah; blah\r\n world\r\n0\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/chunked_w_bullshit_after_length +request_url :/chunked_w_bullshit_after_length +fragment : +query_string: +body :"hello world" +body_size :0 +header_0 :{ "Transfer-Encoding": "chunked"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :with quotes +raw :"GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/with_"stupid"_quotes +request_url :/with_"stupid"_quotes?foo="bar" +fragment : +query_string:foo="bar" +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :apachebench get +raw :"GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/test +request_url :/test +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Host": "0.0.0.0:5000"} +header_1 :{ "User-Agent": "ApacheBench/2.3"} +header_2 :{ "Accept": "*/*"} +should_keep_alive :0 +http_major :1 +http_minor :0 + +name :query url with question mark +raw :"GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/test.cgi +request_url :/test.cgi?foo=bar?baz +fragment : +query_string:foo=bar?baz +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :newline prefix get +raw :"\r\nGET /test HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/test +request_url :/test +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :upgrade request +raw :"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/demo +request_url :/demo +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Host": "example.com"} +header_1 :{ "Connection": "Upgrade"} +header_2 :{ "Sec-WebSocket-Key2": "12998 5 Y3 1 .P00"} +header_3 :{ "Sec-WebSocket-Protocol": "sample"} +header_4 :{ "Upgrade": "WebSocket"} +header_5 :{ "Sec-WebSocket-Key1": "4 @1 46546xW%0l 1 5"} +header_6 :{ "Origin": "http://example.com"} +should_keep_alive :1 +upgrade :"Hot diggity dogg" +http_major :1 +http_minor :1 + +name :connect request +raw :"CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\r\nand yet even more data" +type :HTTP_REQUEST +method: HTTP_CONNECT +status_code :0 +request_path: +request_url :0-home0.netscape.com:443 +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "User-agent": "Mozilla/1.1N"} +header_1 :{ "Proxy-authorization": "basic aGVsbG86d29ybGQ="} +should_keep_alive :0 +upgrade :"some data\r\nand yet even more data" +http_major :1 +http_minor :0 + +name :report request +raw :"REPORT /test HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_REPORT +status_code :0 +request_path:/test +request_url :/test +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :request with no http version +raw :"GET /\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/ +request_url :/ +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :0 +http_major :0 +http_minor :9 + +name :m-search request +raw :"M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_MSEARCH +status_code :0 +request_path:* +request_url :* +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "HOST": "239.255.255.250:1900"} +header_1 :{ "MAN": ""ssdp:discover""} +header_2 :{ "ST": ""ssdp:all""} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :line folding in header value +raw :"GET / HTTP/1.1\r\nLine1: abc\r\n def\r\n ghi\r\n jkl\r\n mno \r\n qrs\r\nLine2: line2 \r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/ +request_url :/ +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Line1": "abcdefghijklmno qrs"} +header_1 :{ "Line2": "line2 "} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :host terminated by a query string +raw :"GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path: +request_url :http://hypnotoad.org?hail=all +fragment : +query_string:hail=all +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :host:port terminated by a query string +raw :"GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path: +request_url :http://hypnotoad.org:1234?hail=all +fragment : +query_string:hail=all +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :host:port terminated by a space +raw :"GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path: +request_url :http://hypnotoad.org:1234 +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :PATCH request +raw :"PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc" +type :HTTP_REQUEST +method: UNKNOWN +status_code :0 +request_path:/file.txt +request_url :/file.txt +fragment : +query_string: +body :"cccccccccc" +body_size :0 +header_0 :{ "Host": "www.example.com"} +header_1 :{ "Content-Type": "application/example"} +header_2 :{ "If-Match": ""e0023aa4e""} +header_3 :{ "Content-Length": "10"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :connect caps request +raw :"CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_CONNECT +status_code :0 +request_path: +request_url :HOME0.NETSCAPE.COM:443 +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "User-agent": "Mozilla/1.1N"} +header_1 :{ "Proxy-authorization": "basic aGVsbG86d29ybGQ="} +should_keep_alive :0 +upgrade :"" +http_major :1 +http_minor :0 + +name :eat CRLF between requests, no "Connection: close" header +raw :"POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\n\r\nq=42\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/ +request_url :/ +fragment : +query_string: +body :"q=42" +body_size :0 +header_0 :{ "Host": "www.example.com"} +header_1 :{ "Content-Type": "application/x-www-form-urlencoded"} +header_2 :{ "Content-Length": "4"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :eat CRLF between requests even if "Connection: close" is set +raw :"POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\nConnection: close\r\n\r\nq=42\r\n" +type :HTTP_REQUEST +method: HTTP_POST +status_code :0 +request_path:/ +request_url :/ +fragment : +query_string: +body :"q=42" +body_size :0 +header_0 :{ "Host": "www.example.com"} +header_1 :{ "Content-Type": "application/x-www-form-urlencoded"} +header_2 :{ "Content-Length": "4"} +header_3 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :PURGE request +raw :"PURGE /file.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n" +type :HTTP_REQUEST +method: UNKNOWN +status_code :0 +request_path:/file.txt +request_url :/file.txt +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Host": "www.example.com"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :google 301 +raw :"HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219 \r\n\r\n\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :301 +request_path: +request_url : +fragment : +query_string: +body :"\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n" +body_size :0 +header_0 :{ "Location": "http://www.google.com/"} +header_1 :{ "Content-Type": "text/html; charset=UTF-8"} +header_2 :{ "Date": "Sun, 26 Apr 2009 11:11:49 GMT"} +header_3 :{ "Expires": "Tue, 26 May 2009 11:11:49 GMT"} +header_4 :{ "X-$PrototypeBI-Version": "1.6.0.3"} +header_5 :{ "Cache-Control": "public, max-age=2592000"} +header_6 :{ "Server": "gws"} +header_7 :{ "Content-Length": "219 "} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :no content-length response +raw :"HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n" +body_size :0 +header_0 :{ "Date": "Tue, 04 Aug 2009 07:59:32 GMT"} +header_1 :{ "Server": "Apache"} +header_2 :{ "X-Powered-By": "Servlet/2.5 JSP/2.1"} +header_3 :{ "Content-Type": "text/xml; charset=utf-8"} +header_4 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :404 no headers no body +raw :"HTTP/1.1 404 Not Found\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :404 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :301 no response phrase +raw :"HTTP/1.1 301\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :301 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :200 trailing space on chunked body +raw :"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"This is the data in the first chunk\r\nand this is the second one\r\n" +body_size :65 +header_0 :{ "Content-Type": "text/plain"} +header_1 :{ "Transfer-Encoding": "chunked"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :no carriage ret +raw :"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"these headers are from http://news.ycombinator.com/" +body_size :0 +header_0 :{ "Content-Type": "text/html; charset=utf-8"} +header_1 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :proxy connection +raw :"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"hello world" +body_size :0 +header_0 :{ "Content-Type": "text/html; charset=UTF-8"} +header_1 :{ "Content-Length": "11"} +header_2 :{ "Proxy-Connection": "close"} +header_3 :{ "Date": "Thu, 31 Dec 2009 20:55:48 +0000"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :underscore header key +raw :"HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Server": "DCLK-AdSvr"} +header_1 :{ "Content-Type": "text/xml"} +header_2 :{ "Content-Length": "0"} +header_3 :{ "DCLK_imp": "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o"} +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :bonjourmadame.fr +raw :"HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :301 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Date": "Thu, 03 Jun 2010 09:56:32 GMT"} +header_1 :{ "Server": "Apache/2.2.3 (Red Hat)"} +header_2 :{ "Cache-Control": "public"} +header_3 :{ "Pragma": ""} +header_4 :{ "Location": "http://www.bonjourmadame.fr/"} +header_5 :{ "Vary": "Accept-Encoding"} +header_6 :{ "Content-Length": "0"} +header_7 :{ "Content-Type": "text/html; charset=UTF-8"} +header_8 :{ "Connection": "keep-alive"} +should_keep_alive :1 +http_major :1 +http_minor :0 + +name :field underscore +raw :"HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Date": "Tue, 28 Sep 2010 01:14:13 GMT"} +header_1 :{ "Server": "Apache"} +header_2 :{ "Cache-Control": "no-cache, must-revalidate"} +header_3 :{ "Expires": "Mon, 26 Jul 1997 05:00:00 GMT"} +header_4 :{ ".et-Cookie": "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com"} +header_5 :{ "Vary": "Accept-Encoding"} +header_6 :{ "_eep-Alive": "timeout=45"} +header_7 :{ "_onnection": "Keep-Alive"} +header_8 :{ "Transfer-Encoding": "chunked"} +header_9 :{ "Content-Type": "text/html"} +header_10 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :non-ASCII in status line +raw :"HTTP/1.1 500 Oriëntatieprobleem\r\nDate: Fri, 5 Nov 2010 23:07:12 GMT+2\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :500 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Date": "Fri, 5 Nov 2010 23:07:12 GMT+2"} +header_1 :{ "Content-Length": "0"} +header_2 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :http version 0.9 +raw :"HTTP/0.9 200 OK\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :0 +http_major :0 +http_minor :9 + +name :neither content-length nor transfer-encoding response +raw :"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"hello world" +body_size :0 +header_0 :{ "Content-Type": "text/plain"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :HTTP/1.0 with keep-alive and EOF-terminated 200 status +raw :"HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Connection": "keep-alive"} +should_keep_alive :0 +http_major :1 +http_minor :0 + +name :HTTP/1.0 with keep-alive and a 204 status +raw :"HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :204 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Connection": "keep-alive"} +should_keep_alive :1 +http_major :1 +http_minor :0 + +name :HTTP/1.1 with an EOF-terminated 200 status +raw :"HTTP/1.1 200 OK\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :HTTP/1.1 with a 204 status +raw :"HTTP/1.1 204 No content\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :204 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +should_keep_alive :1 +http_major :1 +http_minor :1 + +name :HTTP/1.1 with a 204 status and keep-alive disabled +raw :"HTTP/1.1 204 No content\r\nConnection: close\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :204 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Connection": "close"} +should_keep_alive :0 +http_major :1 +http_minor :1 + +name :HTTP/1.1 with chunked endocing and a 200 response +raw :"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n" +type :HTTP_RESPONSE +method: HTTP_DELETE +status_code :200 +request_path: +request_url : +fragment : +query_string: +body :"" +body_size :0 +header_0 :{ "Transfer-Encoding": "chunked"} +should_keep_alive :1 +http_major :1 +http_minor :1 + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 new file mode 100644 index 0000000..5266159 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 @@ -0,0 +1,17 @@ +name :utf-8 path request +raw :"GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n" +type :HTTP_REQUEST +method: HTTP_GET +status_code :0 +request_path:/δ¶/δt/pope +request_url :/δ¶/δt/pope?q=1#narf +fragment :narf +query_string:q=1 +body :"" +body_size :0 +header_0 :{ "Host": "github.com"} +should_keep_alive :1 +upgrade :0 +http_major :1 +http_minor :1 + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb new file mode 100644 index 0000000..1604890 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb @@ -0,0 +1,6 @@ + +"A".upto("Z") {|c| + puts "public static final byte #{c} = 0x#{c[0].to_s(16)};" +} + + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb new file mode 100644 index 0000000..84f9699 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb @@ -0,0 +1,13 @@ + + +def printbytes str +str.each_byte { |b| + print "0x#{b.to_s(16)}, " +} +end + +if $0 == __FILE__ + printf "static final byte [] #{ARGV[0]} = {\n" + printbytes ARGV[0] + printf "\n};\n" +end diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb new file mode 100644 index 0000000..13960cb --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb @@ -0,0 +1,15 @@ + + +0.upto(255) { |i| + printf "\n" if i%16 == 0 + printf " " if i%8 == 0 + s = ("" << i) + if s =~ /[A-Z0-9\-_\/ ]/ + print "0x#{i.to_s(16)}," + elsif s =~ /[a-z]/ + print "0x#{s.upcase[0].to_s(16)}," + else + print "0x00," + end + +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb new file mode 100644 index 0000000..683adb9 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb @@ -0,0 +1,33 @@ + + + + +# name : 200 trailing space on chunked body +# raw : "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n" +# type : HTTP_RESPONSE +# method: HTTP_DELETE +# status code :200 +# request_path: +# request_url : +# fragment : +# query_string: +# body :"This is the data in the first chunk\r\nand this is the second one\r\n" +# body_size :65 +# header_0 :{ "Content-Type": "text/plain"} +# header_1 :{ "Transfer-Encoding": "chunked"} +# should_keep_alive :1 +# upgrade :0 +# http_major :1 +# http_minor :1 + + +class ParserTest + attr_accessor :name + attr_accessor :raw + attr_accessor :type + attr_accessor :method + attr_accessor :status_code + attr_accessor :request_path + attr_accessor :method +end + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/AUTHORS b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/AUTHORS new file mode 100644 index 0000000..abe99de --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/AUTHORS @@ -0,0 +1,32 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +LE ROUX Thomas +Randy Rizun diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/CONTRIBUTIONS b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/CONTRIBUTIONS new file mode 100644 index 0000000..11ba31e --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/CONTRIBUTIONS @@ -0,0 +1,4 @@ +Contributors must agree to the Contributor License Agreement before patches +can be accepted. + +http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT new file mode 100644 index 0000000..58010b3 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/README.md b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/README.md new file mode 100644 index 0000000..700c3ac --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/README.md @@ -0,0 +1,178 @@ +HTTP Parser +=========== + +This is a parser for HTTP messages written in C. It parses both requests and +responses. The parser is designed to be used in performance HTTP +applications. It does not make any syscalls nor allocations, it does not +buffer data, it can be interrupted at anytime. Depending on your +architecture, it only requires about 40 bytes of data per message +stream (in a web server that is per connection). + +Features: + + * No dependencies + * Handles persistent streams (keep-alive). + * Decodes chunked encoding. + * Upgrade support + * Defends against buffer overflow attacks. + +The parser extracts the following information from HTTP messages: + + * Header fields and values + * Content-Length + * Request method + * Response status code + * Transfer-Encoding + * HTTP version + * Request URL + * Message body + + +Usage +----- + +One `http_parser` object is used per TCP connection. Initialize the struct +using `http_parser_init()` and set the callbacks. That might look something +like this for a request parser: + + http_parser_settings settings; + settings.on_path = my_path_callback; + settings.on_header_field = my_header_field_callback; + /* ... */ + + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); + parser->data = my_socket; + +When data is received on the socket execute the parser and check for errors. + + size_t len = 80*1024, nparsed; + char buf[len]; + ssize_t recved; + + recved = recv(fd, buf, len, 0); + + if (recved < 0) { + /* Handle error. */ + } + + /* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been recieved. + */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + if (parser->upgrade) { + /* handle new protocol */ + } else if (nparsed != recved) { + /* Handle error. Usually just close the connection. */ + } + +HTTP needs to know where the end of the stream is. For example, sometimes +servers send responses without Content-Length and expect the client to +consume input (for the body) until EOF. To tell http_parser about EOF, give +`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors +can still be encountered during an EOF, so one must still be prepared +to receive them. + +Scalar valued message information such as `status_code`, `method`, and the +HTTP version are stored in the parser structure. This data is only +temporally stored in `http_parser` and gets reset on each new message. If +this information is needed later, copy it out of the structure during the +`headers_complete` callback. + +The parser decodes the transfer-encoding for both requests and responses +transparently. That is, a chunked encoding is decoded before being sent to +the on_body callback. + + +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the Web Socket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more +information the Web Socket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body. Issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() will stop parsing at the end of the headers and return. + +The user is expected to check if `parser->upgrade` has been set to 1 after +`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied +offset by the return value of `http_parser_execute()`. + + +Callbacks +--------- + +During the `http_parser_execute()` call, the callbacks set in +`http_parser_settings` will be executed. The parser maintains state and +never looks behind, so buffering the data is not necessary. If you need to +save certain data for later usage, you can do that from the callbacks. + +There are two types of callbacks: + +* notification `typedef int (*http_cb) (http_parser*);` + Callbacks: on_message_begin, on_headers_complete, on_message_complete. +* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` + Callbacks: (requests only) on_uri, + (common) on_header_field, on_header_value, on_body; + +Callbacks must return 0 on success. Returning a non-zero value indicates +error to the parser, making it exit immediately. + +In case you parse HTTP message in chunks (i.e. `read()` request line +from socket, parse, read half headers, parse, etc) your data callbacks +may be called more than once. Http-parser guarantees that data pointer is only +valid for the lifetime of callback. You can also `read()` into a heap allocated +buffer to avoid copying memory around if this fits your application. + +Reading headers may be a tricky task if you read/parse headers partially. +Basically, you need to remember whether last header callback was field or value +and apply following logic: + + (on_header_field and on_header_value shortened to on_h_*) + ------------------------ ------------ -------------------------------------------- + | State (prev. callback) | Callback | Description/action | + ------------------------ ------------ -------------------------------------------- + | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | + | | | into it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_field | New header started. | + | | | Copy current name,value buffers to headers | + | | | list and allocate new buffer for new name | + ------------------------ ------------ -------------------------------------------- + | field | on_h_field | Previous name continues. Reallocate name | + | | | buffer and append callback data to it | + ------------------------ ------------ -------------------------------------------- + | field | on_h_value | Value for current header started. Allocate | + | | | new buffer and copy callback data to it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_value | Value continues. Reallocate value buffer | + | | | and append callback data to it | + ------------------------ ------------ -------------------------------------------- + + +Parsing URLs +------------ + +A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. +Users of this library may wish to use it to parse URLs constructed from +consecutive `on_url` callbacks. + +See examples of reading in headers: + +* [partial example](http://gist.github.com/155877) in C +* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C +* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.c b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.c new file mode 100644 index 0000000..f2ca661 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.c @@ -0,0 +1,2058 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#if HTTP_PARSER_DEBUG +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ + parser->error_lineno = __LINE__; \ +} while (0) +#else +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { "DELETE" + , "GET" + , "HEAD" + , "POST" + , "PUT" + , "CONNECT" + , "OPTIONS" + , "TRACE" + , "COPY" + , "LOCK" + , "MKCOL" + , "MOVE" + , "PROPFIND" + , "PROPPATCH" + , "UNLOCK" + , "REPORT" + , "MKACTIVITY" + , "CHECKOUT" + , "MERGE" + , "M-SEARCH" + , "NOTIFY" + , "SUBSCRIBE" + , "UNSUBSCRIBE" + , "PATCH" + , "PURGE" + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +static const uint8_t normal_url_char[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, 1, 1, 0, 1, 1, 1, 1, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1, 1, 1, 1, 1, 1, 1, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1, 1, 1, 1, 1, 1, 1, 0, }; + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_host_start + , s_req_host_v6_start + , s_req_host_v6 + , s_req_host_v6_end + , s_req_host + , s_req_port_start + , s_req_port + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + assert(!isspace(ch)); + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_host_start; + } + + break; + + case s_req_host_start: + if (ch == '[') { + return s_req_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_req_host; + } + + break; + + case s_req_host: + if (IS_HOST_CHAR(ch)) { + return s_req_host; + } + + /* FALLTHROUGH */ + case s_req_host_v6_end: + switch (ch) { + case ':': + return s_req_port_start; + + case '/': + return s_req_path; + + case '?': + return s_req_query_string_start; + } + + break; + + case s_req_host_v6: + if (ch == ']') { + return s_req_host_v6_end; + } + + /* FALLTHROUGH */ + case s_req_host_v6_start: + if (IS_HEX(ch) || ch == ':') { + return s_req_host_v6; + } + break; + + case s_req_port: + switch (ch) { + case '/': + return s_req_path; + + case '?': + return s_req_query_string_start; + } + + /* FALLTHROUGH */ + case s_req_port_start: + if (IS_NUM(ch)) { + return s_req_port; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (parser->state) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (parser->state == s_header_field) + header_field_mark = data; + if (parser->state == s_header_value) + header_value_mark = data; + switch (parser->state) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_host_v6_end: + case s_req_host: + case s_req_port_start: + case s_req_port: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(parser->state)) { + ++parser->nread; + /* Buffer overflow attack */ + if (parser->nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + reexecute_byte: + switch (parser->state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (ch == CR || ch == LF) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + parser->state = s_res_or_resp_H; + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + parser->state = s_start_req; + goto reexecute_byte; + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + parser->state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + parser->state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + parser->state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '0' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + parser->state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + parser->state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + parser->state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + parser->state = s_res_status; + break; + case CR: + parser->state = s_res_line_almost_done; + break; + case LF: + parser->state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + parser->state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + parser->state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + parser->state = s_req_method; + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (parser->index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (parser->index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (parser->index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (parser->index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (parser->index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + goto error; + } + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') parser->method = HTTP_PURGE; + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + parser->state = s_req_host_start; + } + + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_port_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_host: + case s_req_host_v6_end: + case s_req_port: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + parser->state = s_req_http_start; + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + parser->state = (ch == CR) ? + s_req_line_almost_done : + s_header_field_start; + CALLBACK_DATA(url); + break; + default: + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + parser->state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + parser->state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + parser->state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + parser->state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + parser->state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + parser->state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + parser->state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + parser->state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + parser->state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == CR) { + parser->state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + parser->state = s_headers_almost_done; + goto reexecute_byte; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + parser->state = s_header_field; + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + parser->state = s_header_value_start; + CALLBACK_DATA(header_field); + break; + } + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_field); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_start: + { + if (ch == ' ' || ch == '\t') break; + + MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + + if (ch == CR) { + parser->header_state = h_general; + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_field_start; + CALLBACK_DATA(header_value); + break; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else { + parser->header_state = h_general; + } + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + parser->state = s_header_almost_done; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } + + c = LOWER(ch); + + switch (parser->header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + parser->header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + parser->header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CLOSE)-2) { + parser->header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + parser->state = s_header_value; + parser->header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + { + STRICT_CHECK(ch != LF); + + parser->state = s_header_value_lws; + + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + parser->state = s_header_value_start; + else + { + parser->state = s_header_field_start; + goto reexecute_byte; + } + break; + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + } + + parser->state = s_headers_done; + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return p - data; + } + + goto reexecute_byte; + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + parser->state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + parser->state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + parser->state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + goto reexecute_byte; + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + parser->state = s_chunk_size; + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + parser->state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + parser->state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + parser->state = s_header_field_start; + } else { + parser->state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + parser->state = s_chunk_data_done; + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + parser->state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * http_method_str (enum http_method m) +{ + return method_strings[m]; +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + + u->port = u->field_set = 0; + s = is_connect ? s_req_host_start : s_req_spaces_before_url; + uf = old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_host_start: + case s_req_host_v6_start: + case s_req_host_v6_end: + case s_req_port_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_host: + case s_req_host_v6: + uf = UF_HOST; + break; + + case s_req_port: + uf = UF_PORT; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_req_host_v6_start: + case s_req_host_v6: + case s_req_host_v6_end: + case s_req_host: + case s_req_port_start: + return 1; + default: + break; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp new file mode 100644 index 0000000..c6eada7 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp @@ -0,0 +1,79 @@ +# This file is used with the GYP meta build system. +# http://code.google.com/p/gyp/ +# To build try this: +# svn co http://gyp.googlecode.com/svn/trunk gyp +# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp +# ./out/Debug/test +{ + 'target_defaults': { + 'default_configuration': 'Debug', + 'configurations': { + # TODO: hoist these out and put them somewhere common, because + # RuntimeLibrary MUST MATCH across the entire project + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 1, # static debug + }, + }, + }, + 'Release': { + 'defines': [ 'NDEBUG' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 0, # static release + }, + }, + } + }, + 'msvs_settings': { + 'VCCLCompilerTool': { + }, + 'VCLibrarianTool': { + }, + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + }, + }, + 'conditions': [ + ['OS == "win"', { + 'defines': [ + 'WIN32' + ], + }] + ], + }, + + 'targets': [ + { + 'target_name': 'http_parser', + 'type': 'static_library', + 'include_dirs': [ '.' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + }, + 'defines': [ 'HTTP_PARSER_STRICT=0' ], + 'sources': [ './http_parser.c', ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. http_parser.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + }, + }] + ], + }, + + { + 'target_name': 'test', + 'type': 'executable', + 'dependencies': [ 'http_parser' ], + 'sources': [ 'test.c' ] + } + ] +} + diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.h b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.h new file mode 100644 index 0000000..78b3701 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/http_parser.h @@ -0,0 +1,312 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_PARSER_VERSION_MAJOR 1 +#define HTTP_PARSER_VERSION_MINOR 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +typedef unsigned int size_t; +typedef int ssize_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to + * the error reporting facility. + */ +#ifndef HTTP_PARSER_DEBUG +# define HTTP_PARSER_DEBUG 0 +#endif + + +/* Maximium header size allowed */ +#define HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; +typedef struct http_parser_result http_parser_result; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_path" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +enum http_method + { HTTP_DELETE = 0 + , HTTP_GET + , HTTP_HEAD + , HTTP_POST + , HTTP_PUT + /* pathological */ + , HTTP_CONNECT + , HTTP_OPTIONS + , HTTP_TRACE + /* webdav */ + , HTTP_COPY + , HTTP_LOCK + , HTTP_MKCOL + , HTTP_MOVE + , HTTP_PROPFIND + , HTTP_PROPPATCH + , HTTP_UNLOCK + /* subversion */ + , HTTP_REPORT + , HTTP_MKACTIVITY + , HTTP_CHECKOUT + , HTTP_MERGE + /* upnp */ + , HTTP_MSEARCH + , HTTP_NOTIFY + , HTTP_SUBSCRIBE + , HTTP_UNSUBSCRIBE + /* RFC-5789 */ + , HTTP_PATCH + , HTTP_PURGE + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + +/* Get the line number that generated the current error */ +#if HTTP_PARSER_DEBUG +#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) +#else +#define HTTP_PARSER_ERRNO_LINE(p) 0 +#endif + + +struct http_parser { + /** PRIVATE **/ + unsigned char type : 2; /* enum http_parser_type */ + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; /* enum state from http_parser.c */ + unsigned char header_state; /* enum header_state from http_parser.c */ + unsigned char index; /* index into current matcher */ + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + unsigned char http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned char upgrade : 1; + +#if HTTP_PARSER_DEBUG + uint32_t error_lineno; +#endif + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_MAX = 6 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/test.c b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/test.c new file mode 100644 index 0000000..184ba24 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/test.c @@ -0,0 +1,2876 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include /* rand */ +#include +#include + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 + +#define MAX_HEADERS 13 +#define MAX_ELEMENT_SIZE 500 + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +static http_parser *parser; + +struct message { + const char *name; // for debugging purposes + const char *raw; + enum http_parser_type type; + enum http_method method; + int status_code; + char request_path[MAX_ELEMENT_SIZE]; + char request_url[MAX_ELEMENT_SIZE]; + char fragment[MAX_ELEMENT_SIZE]; + char query_string[MAX_ELEMENT_SIZE]; + char body[MAX_ELEMENT_SIZE]; + size_t body_size; + uint16_t port; + int num_headers; + enum { NONE=0, FIELD, VALUE } last_header_element; + char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; + int should_keep_alive; + + const char *upgrade; // upgraded body + + unsigned short http_major; + unsigned short http_minor; + + int message_begin_cb_called; + int headers_complete_cb_called; + int message_complete_cb_called; + int message_complete_on_eof; +}; + +static int currently_parsing_eof; + +static struct message messages[5]; +static int num_messages; +static http_parser_settings *current_pause_parser; + +/* * R E Q U E S T S * */ +const struct message requests[] = +#define CURL_GET 0 +{ {.name= "curl get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.1\r\n" + "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= + { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } + , { "Host", "0.0.0.0=5000" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define FIREFOX_GET 1 +, {.name= "firefox get" + ,.type= HTTP_REQUEST + ,.raw= "GET /favicon.ico HTTP/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Keep-Alive: 300\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/favicon.ico" + ,.request_url= "/favicon.ico" + ,.num_headers= 8 + ,.headers= + { { "Host", "0.0.0.0=5000" } + , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } + , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } + , { "Accept-Language", "en-us,en;q=0.5" } + , { "Accept-Encoding", "gzip,deflate" } + , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } + , { "Keep-Alive", "300" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define DUMBFUCK 2 +, {.name= "dumbfuck" + ,.type= HTTP_REQUEST + ,.raw= "GET /dumbfuck HTTP/1.1\r\n" + "aaaaaaaaaaaaa:++++++++++\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/dumbfuck" + ,.request_url= "/dumbfuck" + ,.num_headers= 1 + ,.headers= + { { "aaaaaaaaaaaaa", "++++++++++" } + } + ,.body= "" + } + +#define FRAGMENT_IN_URI 3 +, {.name= "fragment in url" + ,.type= HTTP_REQUEST + ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "page=1" + ,.fragment= "posts-17408" + ,.request_path= "/forums/1/topics/2375" + /* XXX request url does include fragment? */ + ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_NO_HEADERS_NO_BODY 4 +, {.name= "get no headers no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_no_headers_no_body/world" + ,.request_url= "/get_no_headers_no_body/world" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_ONE_HEADER_NO_BODY 5 +, {.name= "get one header no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_one_header_no_body" + ,.request_url= "/get_one_header_no_body" + ,.num_headers= 1 + ,.headers= + { { "Accept" , "*/*" } + } + ,.body= "" + } + +#define GET_FUNKY_CONTENT_LENGTH 6 +, {.name= "get funky content length body hello" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" + "conTENT-Length: 5\r\n" + "\r\n" + "HELLO" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_funky_content_length_body_hello" + ,.request_url= "/get_funky_content_length_body_hello" + ,.num_headers= 1 + ,.headers= + { { "conTENT-Length" , "5" } + } + ,.body= "HELLO" + } + +#define POST_IDENTITY_BODY_WORLD 7 +, {.name= "post identity body world" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" + "Accept: */*\r\n" + "Transfer-Encoding: identity\r\n" + "Content-Length: 5\r\n" + "\r\n" + "World" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "q=search" + ,.fragment= "hey" + ,.request_path= "/post_identity_body_world" + ,.request_url= "/post_identity_body_world?q=search#hey" + ,.num_headers= 3 + ,.headers= + { { "Accept", "*/*" } + , { "Transfer-Encoding", "identity" } + , { "Content-Length", "5" } + } + ,.body= "World" + } + +#define POST_CHUNKED_ALL_YOUR_BASE 8 +, {.name= "post - chunked body: all your base are belong to us" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1e\r\nall your base are belong to us\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/post_chunked_all_your_base" + ,.request_url= "/post_chunked_all_your_base" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding" , "chunked" } + } + ,.body= "all your base are belong to us" + } + +#define TWO_CHUNKS_MULT_ZERO_END 9 +, {.name= "two chunks ; triple zero ending" + ,.type= HTTP_REQUEST + ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "000\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/two_chunks_mult_zero_end" + ,.request_url= "/two_chunks_mult_zero_end" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + } + +#define CHUNKED_W_TRAILING_HEADERS 10 +, {.name= "chunked with trailing headers. blech." + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "0\r\n" + "Vary: *\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_trailing_headers" + ,.request_url= "/chunked_w_trailing_headers" + ,.num_headers= 3 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Vary", "*" } + , { "Content-Type", "text/plain" } + } + ,.body= "hello world" + } + +#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 +, {.name= "with bullshit after the length" + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" + "6; blahblah; blah\r\n world\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_bullshit_after_length" + ,.request_url= "/chunked_w_bullshit_after_length" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + } + +#define WITH_QUOTES 12 +, {.name= "with quotes" + ,.type= HTTP_REQUEST + ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=\"bar\"" + ,.fragment= "" + ,.request_path= "/with_\"stupid\"_quotes" + ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define APACHEBENCH_GET 13 +/* The server receiving this request SHOULD NOT wait for EOF + * to know that content-length == 0. + * How to represent this in a unit test? message_complete_on_eof + * Compare with NO_CONTENT_LENGTH_RESPONSE. + */ +, {.name = "apachebench get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.0\r\n" + "Host: 0.0.0.0:5000\r\n" + "User-Agent: ApacheBench/2.3\r\n" + "Accept: */*\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= { { "Host", "0.0.0.0:5000" } + , { "User-Agent", "ApacheBench/2.3" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define QUERY_URL_WITH_QUESTION_MARK_GET 14 +/* Some clients include '?' characters in query strings. + */ +, {.name = "query url with question mark" + ,.type= HTTP_REQUEST + ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=bar?baz" + ,.fragment= "" + ,.request_path= "/test.cgi" + ,.request_url= "/test.cgi?foo=bar?baz" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define PREFIX_NEWLINE_GET 15 +/* Some clients, especially after a POST in a keep-alive connection, + * will send an extra CRLF before the next request + */ +, {.name = "newline prefix get" + ,.type= HTTP_REQUEST + ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define UPGRADE_REQUEST 16 +, {.name = "upgrade request" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Upgrade" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + +#define CONNECT_REQUEST 17 +, {.name = "connect request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + "some data\r\n" + "and yet even more data" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "0-home0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="some data\r\nand yet even more data" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#define REPORT_REQ 18 +, {.name= "report request" + ,.type= HTTP_REQUEST + ,.raw= "REPORT /test HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_REPORT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define NO_HTTP_VERSION 19 +, {.name= "request with no http version" + ,.type= HTTP_REQUEST + ,.raw= "GET /\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 0 + ,.http_minor= 9 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define MSEARCH_REQ 20 +, {.name= "m-search request" + ,.type= HTTP_REQUEST + ,.raw= "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "ST: \"ssdp:all\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_MSEARCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "*" + ,.request_url= "*" + ,.num_headers= 3 + ,.headers= { { "HOST", "239.255.255.250:1900" } + , { "MAN", "\"ssdp:discover\"" } + , { "ST", "\"ssdp:all\"" } + } + ,.body= "" + } + +#define LINE_FOLDING_IN_HEADER 20 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\r\n" + "Line1: abc\r\n" + "\tdef\r\n" + " ghi\r\n" + "\t\tjkl\r\n" + " mno \r\n" + "\t \tqrs\r\n" + "Line2: \t line2\t\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 2 + ,.headers= { { "Line1", "abcdefghijklmno qrs" } + , { "Line2", "line2\t" } + } + ,.body= "" + } + + +#define QUERY_TERMINATED_HOST 21 +, {.name= "host terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org?hail=all" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define QUERY_TERMINATED_HOSTPORT 22 +, {.name= "host:port terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234?hail=all" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define SPACE_TERMINATED_HOSTPORT 23 +, {.name= "host:port terminated by a space" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define PATCH_REQ 24 +, {.name = "PATCH request" + ,.type= HTTP_REQUEST + ,.raw= "PATCH /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/example\r\n" + "If-Match: \"e0023aa4e\"\r\n" + "Content-Length: 10\r\n" + "\r\n" + "cccccccccc" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PATCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 4 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/example" } + , { "If-Match", "\"e0023aa4e\"" } + , { "Content-Length", "10" } + } + ,.body= "cccccccccc" + } + +#define CONNECT_CAPS_REQUEST 25 +, {.name = "connect caps request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "HOME0.NETSCAPE.COM:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#if !HTTP_PARSER_STRICT +#define UTF8_PATH_REQ 26 +, {.name= "utf-8 path request" + ,.type= HTTP_REQUEST + ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" + "Host: github.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "q=1" + ,.fragment= "narf" + ,.request_path= "/δ¶/δt/pope" + ,.request_url= "/δ¶/δt/pope?q=1#narf" + ,.num_headers= 1 + ,.headers= { {"Host", "github.com" } + } + ,.body= "" + } + +#define HOSTNAME_UNDERSCORE 27 +, {.name = "hostname underscore" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "home_0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } +#endif /* !HTTP_PARSER_STRICT */ + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 28 +, {.name = "eat CRLF between requests, no \"Connection: close\" header" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 3 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + } + ,.body= "q=42" + } + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 29 +, {.name = "eat CRLF between requests even if \"Connection: close\" is set" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "Connection: close\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 4 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + , { "Connection", "close" } + } + ,.body= "q=42" + } + +#define PURGE_REQ 30 +, {.name = "PURGE request" + ,.type= HTTP_REQUEST + ,.raw= "PURGE /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PURGE + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 1 + ,.headers= { { "Host", "www.example.com" } } + ,.body= "" + } + +, {.name= NULL } /* sentinel */ +}; + +/* * R E S P O N S E S * */ +const struct message responses[] = +#define GOOGLE_301 0 +{ {.name= "google 301" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" + "Location: http://www.google.com/\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" + "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" + "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ + "Cache-Control: public, max-age=2592000\r\n" + "Server: gws\r\n" + "Content-Length: 219 \r\n" + "\r\n" + "\n" + "301 Moved\n" + "

301 Moved

\n" + "The document has moved\n" + "here.\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.num_headers= 8 + ,.headers= + { { "Location", "http://www.google.com/" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } + , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } + , { "X-$PrototypeBI-Version", "1.6.0.3" } + , { "Cache-Control", "public, max-age=2592000" } + , { "Server", "gws" } + , { "Content-Length", "219 " } + } + ,.body= "\n" + "301 Moved\n" + "

301 Moved

\n" + "The document has moved\n" + "here.\r\n" + "\r\n" + } + +#define NO_CONTENT_LENGTH_RESPONSE 1 +/* The client should wait for the server's EOF. That is, when content-length + * is not specified, and "Connection: close", the end of body is specified + * by the EOF. + * Compare with APACHEBENCH_GET + */ +, {.name= "no content-length response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" + "Server: Apache\r\n" + "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" + "Content-Type: text/xml; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n" + "\n" + "\n" + " \n" + " \n" + " SOAP-ENV:Client\n" + " Client Error\n" + " \n" + " \n" + "" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 5 + ,.headers= + { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } + , { "Server", "Apache" } + , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } + , { "Content-Type", "text/xml; charset=utf-8" } + , { "Connection", "close" } + } + ,.body= "\n" + "\n" + " \n" + " \n" + " SOAP-ENV:Client\n" + " Client Error\n" + " \n" + " \n" + "" + } + +#define NO_HEADERS_NO_BODY_404 2 +, {.name= "404 no headers no body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 404 + ,.num_headers= 0 + ,.headers= {} + ,.body_size= 0 + ,.body= "" + } + +#define NO_REASON_PHRASE 3 +, {.name= "301 no response phrase" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301\r\n\r\n" + ,.should_keep_alive = FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define TRAILING_SPACE_ON_CHUNKED_BODY 4 +, {.name="200 trailing space on chunked body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "25 \r\n" + "This is the data in the first chunk\r\n" + "\r\n" + "1C\r\n" + "and this is the second one\r\n" + "\r\n" + "0 \r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/plain" } + , {"Transfer-Encoding", "chunked" } + } + ,.body_size = 37+28 + ,.body = + "This is the data in the first chunk\r\n" + "and this is the second one\r\n" + + } + +#define NO_CARRIAGE_RET 5 +, {.name="no carriage ret" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\n" + "Content-Type: text/html; charset=utf-8\n" + "Connection: close\n" + "\n" + "these headers are from http://news.ycombinator.com/" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/html; charset=utf-8" } + , {"Connection", "close" } + } + ,.body= "these headers are from http://news.ycombinator.com/" + } + +#define PROXY_CONNECTION 6 +, {.name="proxy connection" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Content-Length: 11\r\n" + "Proxy-Connection: close\r\n" + "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 4 + ,.headers= + { {"Content-Type", "text/html; charset=UTF-8" } + , {"Content-Length", "11" } + , {"Proxy-Connection", "close" } + , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} + } + ,.body= "hello world" + } + +#define UNDERSTORE_HEADER_KEY 7 + // shown by + // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" +, {.name="underscore header key" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: DCLK-AdSvr\r\n" + "Content-Type: text/xml\r\n" + "Content-Length: 0\r\n" + "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 4 + ,.headers= + { {"Server", "DCLK-AdSvr" } + , {"Content-Type", "text/xml" } + , {"Content-Length", "0" } + , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } + } + ,.body= "" + } + +#define BONJOUR_MADAME_FR 8 +/* The client should not merge two headers fields when the first one doesn't + * have a value. + */ +, {.name= "bonjourmadame.fr" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" + "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" + "Server: Apache/2.2.3 (Red Hat)\r\n" + "Cache-Control: public\r\n" + "Pragma: \r\n" + "Location: http://www.bonjourmadame.fr/\r\n" + "Vary: Accept-Encoding\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 301 + ,.num_headers= 9 + ,.headers= + { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } + , { "Server", "Apache/2.2.3 (Red Hat)" } + , { "Cache-Control", "public" } + , { "Pragma", "" } + , { "Location", "http://www.bonjourmadame.fr/" } + , { "Vary", "Accept-Encoding" } + , { "Content-Length", "0" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define RES_FIELD_UNDERSCORE 9 +/* Should handle spaces in header fields */ +, {.name= "field underscore" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" + "Server: Apache\r\n" + "Cache-Control: no-cache, must-revalidate\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" + "Vary: Accept-Encoding\r\n" + "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ + "_onnection: Keep-Alive\r\n" /* semantic value ignored */ + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "0\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 11 + ,.headers= + { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } + , { "Server", "Apache" } + , { "Cache-Control", "no-cache, must-revalidate" } + , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } + , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } + , { "Vary", "Accept-Encoding" } + , { "_eep-Alive", "timeout=45" } + , { "_onnection", "Keep-Alive" } + , { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/html" } + , { "Connection", "close" } + } + ,.body= "" + } + +#define NON_ASCII_IN_STATUS_LINE 10 +/* Should handle non-ASCII in status line */ +, {.name= "non-ASCII in status line" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" + "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 500 + ,.num_headers= 3 + ,.headers= + { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } + , { "Content-Length", "0" } + , { "Connection", "close" } + } + ,.body= "" + } + +#define HTTP_VERSION_0_9 11 +/* Should handle HTTP/0.9 */ +, {.name= "http version 0.9" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/0.9 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 0 + ,.http_minor= 9 + ,.status_code= 200 + ,.num_headers= 0 + ,.headers= + {} + ,.body= "" + } + +#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 +/* The client should wait for the server's EOF. That is, when neither + * content-length nor transfer-encoding is specified, the end of body + * is specified by the EOF. + */ +, {.name= "neither content-length nor transfer-encoding response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Content-Type", "text/plain" } + } + ,.body= "hello world" + } + +#define NO_BODY_HTTP10_KA_200 13 +, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 200 OK\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP10_KA_204 14 +, {.name= "HTTP/1.0 with keep-alive and a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 204 No content\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 204 + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_200 15 +, {.name= "HTTP/1.1 with an EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_204 16 +, {.name= "HTTP/1.1 with a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_NOKA_204 17 +, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.num_headers= 1 + ,.headers= + { { "Connection", "close" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_CHUNKED_200 18 +, {.name= "HTTP/1.1 with chunked endocing and a 200 response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body_size= 0 + ,.body= "" + } + +#if !HTTP_PARSER_STRICT +#define SPACE_IN_FIELD_RES 19 +/* Should handle spaces in header fields */ +, {.name= "field space" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: Microsoft-IIS/6.0\r\n" + "X-Powered-By: ASP.NET\r\n" + "en-US Content-Type: text/xml\r\n" /* this is the problem */ + "Content-Type: text/xml\r\n" + "Content-Length: 16\r\n" + "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" + "Connection: keep-alive\r\n" + "\r\n" + "hello" /* fake body */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.num_headers= 7 + ,.headers= + { { "Server", "Microsoft-IIS/6.0" } + , { "X-Powered-By", "ASP.NET" } + , { "en-US Content-Type", "text/xml" } + , { "Content-Type", "text/xml" } + , { "Content-Length", "16" } + , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } + , { "Connection", "keep-alive" } + } + ,.body= "hello" + } +#endif /* !HTTP_PARSER_STRICT */ + +, {.name= NULL } /* sentinel */ +}; + +int +request_url_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strncat(messages[num_messages].request_url, buf, len); + return 0; +} + +int +header_field_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + if (m->last_header_element != FIELD) + m->num_headers++; + + strncat(m->headers[m->num_headers-1][0], buf, len); + + m->last_header_element = FIELD; + + return 0; +} + +int +header_value_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + strncat(m->headers[m->num_headers-1][1], buf, len); + + m->last_header_element = VALUE; + + return 0; +} + +int +body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strncat(messages[num_messages].body, buf, len); + messages[num_messages].body_size += len; + // printf("body_cb: '%s'\n", requests[num_messages].body); + return 0; +} + +int +count_body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + assert(buf); + messages[num_messages].body_size += len; + return 0; +} + +int +message_begin_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].message_begin_cb_called = TRUE; + return 0; +} + +int +headers_complete_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].method = parser->method; + messages[num_messages].status_code = parser->status_code; + messages[num_messages].http_major = parser->http_major; + messages[num_messages].http_minor = parser->http_minor; + messages[num_messages].headers_complete_cb_called = TRUE; + messages[num_messages].should_keep_alive = http_should_keep_alive(parser); + return 0; +} + +int +message_complete_cb (http_parser *p) +{ + assert(p == parser); + if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) + { + fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " + "value in both on_message_complete and on_headers_complete " + "but it doesn't! ***\n\n"); + assert(0); + exit(1); + } + messages[num_messages].message_complete_cb_called = TRUE; + + messages[num_messages].message_complete_on_eof = currently_parsing_eof; + + num_messages++; + return 0; +} + +/* These dontcall_* callbacks exist so that we can verify that when we're + * paused, no additional callbacks are invoked */ +int +dontcall_message_begin_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_body_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_headers_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_headers_complete() called on paused " + "parser ***\n\n"); + exit(1); +} + +int +dontcall_message_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_complete() called on paused " + "parser ***\n\n"); + exit(1); +} + +static http_parser_settings settings_dontcall = + {.on_message_begin = dontcall_message_begin_cb + ,.on_header_field = dontcall_header_field_cb + ,.on_header_value = dontcall_header_value_cb + ,.on_url = dontcall_request_url_cb + ,.on_body = dontcall_body_cb + ,.on_headers_complete = dontcall_headers_complete_cb + ,.on_message_complete = dontcall_message_complete_cb + }; + +/* These pause_* callbacks always pause the parser and just invoke the regular + * callback that tracks content. Before returning, we overwrite the parser + * settings to point to the _dontcall variety so that we can verify that + * the pause actually did, you know, pause. */ +int +pause_message_begin_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_begin_cb(p); +} + +int +pause_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_field_cb(p, buf, len); +} + +int +pause_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_value_cb(p, buf, len); +} + +int +pause_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return request_url_cb(p, buf, len); +} + +int +pause_body_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return body_cb(p, buf, len); +} + +int +pause_headers_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return headers_complete_cb(p); +} + +int +pause_message_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_complete_cb(p); +} + +static http_parser_settings settings_pause = + {.on_message_begin = pause_message_begin_cb + ,.on_header_field = pause_header_field_cb + ,.on_header_value = pause_header_value_cb + ,.on_url = pause_request_url_cb + ,.on_body = pause_body_cb + ,.on_headers_complete = pause_headers_complete_cb + ,.on_message_complete = pause_message_complete_cb + }; + +static http_parser_settings settings = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_body = body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + }; + +static http_parser_settings settings_count_body = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_body = count_body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + }; + +static http_parser_settings settings_null = + {.on_message_begin = 0 + ,.on_header_field = 0 + ,.on_header_value = 0 + ,.on_url = 0 + ,.on_body = 0 + ,.on_headers_complete = 0 + ,.on_message_complete = 0 + }; + +void +parser_init (enum http_parser_type type) +{ + num_messages = 0; + + assert(parser == NULL); + + parser = malloc(sizeof(http_parser)); + + http_parser_init(parser, type); + + memset(&messages, 0, sizeof messages); + +} + +void +parser_free () +{ + assert(parser); + free(parser); + parser = NULL; +} + +size_t parse (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings, buf, len); + return nparsed; +} + +size_t parse_count_body (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings_count_body, buf, len); + return nparsed; +} + +size_t parse_pause (const char *buf, size_t len) +{ + size_t nparsed; + http_parser_settings s = settings_pause; + + currently_parsing_eof = (len == 0); + current_pause_parser = &s; + nparsed = http_parser_execute(parser, current_pause_parser, buf, len); + return nparsed; +} + +static inline int +check_str_eq (const struct message *m, + const char *prop, + const char *expected, + const char *found) { + if ((expected == NULL) != (found == NULL)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %s\n", (expected == NULL) ? "NULL" : expected); + printf(" found %s\n", (found == NULL) ? "NULL" : found); + return 0; + } + if (expected != NULL && 0 != strcmp(expected, found)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected '%s'\n", expected); + printf(" found '%s'\n", found); + return 0; + } + return 1; +} + +static inline int +check_num_eq (const struct message *m, + const char *prop, + int expected, + int found) { + if (expected != found) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %d\n", expected); + printf(" found %d\n", found); + return 0; + } + return 1; +} + +#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ + if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ + if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ +do { \ + char ubuf[256]; \ + \ + if ((u)->field_set & (1 << (fn))) { \ + memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ + (u)->field_data[(fn)].len); \ + ubuf[(u)->field_data[(fn)].len] = '\0'; \ + } else { \ + ubuf[0] = '\0'; \ + } \ + \ + check_str_eq(expected, #prop, expected->prop, ubuf); \ +} while(0) + +int +message_eq (int index, const struct message *expected) +{ + int i; + struct message *m = &messages[index]; + + MESSAGE_CHECK_NUM_EQ(expected, m, http_major); + MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); + + if (expected->type == HTTP_REQUEST) { + MESSAGE_CHECK_NUM_EQ(expected, m, method); + } else { + MESSAGE_CHECK_NUM_EQ(expected, m, status_code); + } + + MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); + MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); + + assert(m->message_begin_cb_called); + assert(m->headers_complete_cb_called); + assert(m->message_complete_cb_called); + + + MESSAGE_CHECK_STR_EQ(expected, m, request_url); + + /* Check URL components; we can't do this w/ CONNECT since it doesn't + * send us a well-formed URL. + */ + if (*m->request_url && m->method != HTTP_CONNECT) { + struct http_parser_url u; + + if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { + fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", + m->request_url); + exit(1); + } + + m->port = (u.field_set & (1 << UF_PORT)) ? + u.port : 0; + + MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); + MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); + MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); + MESSAGE_CHECK_NUM_EQ(expected, m, port); + } + + if (expected->body_size) { + MESSAGE_CHECK_NUM_EQ(expected, m, body_size); + } else { + MESSAGE_CHECK_STR_EQ(expected, m, body); + } + + MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); + + int r; + for (i = 0; i < m->num_headers; i++) { + r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); + if (!r) return 0; + r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); + if (!r) return 0; + } + + MESSAGE_CHECK_STR_EQ(expected, m, upgrade); + + return 1; +} + +/* Given a sequence of varargs messages, return the number of them that the + * parser should successfully parse, taking into account that upgraded + * messages prevent all subsequent messages from being parsed. + */ +size_t +count_parsed_messages(const size_t nmsgs, ...) { + size_t i; + va_list ap; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + if (m->upgrade) { + va_end(ap); + return i + 1; + } + } + + va_end(ap); + return nmsgs; +} + +/* Given a sequence of bytes and the number of these that we were able to + * parse, verify that upgrade bodies are correct. + */ +void +upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { + va_list ap; + size_t i; + size_t off = 0; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + off += strlen(m->raw); + + if (m->upgrade) { + off -= strlen(m->upgrade); + + /* Check the portion of the response after its specified upgrade */ + if (!check_str_eq(m, "upgrade", body + off, body + nread)) { + exit(1); + } + + /* Fix up the response so that message_eq() will verify the beginning + * of the upgrade */ + *(body + nread + strlen(m->upgrade)) = '\0'; + messages[num_messages -1 ].upgrade = body + nread; + + va_end(ap); + return; + } + } + + va_end(ap); + printf("\n\n*** Error: expected a message with upgrade ***\n"); + + exit(1); +} + +static void +print_error (const char *raw, size_t error_location) +{ + fprintf(stderr, "\n*** %s:%d -- %s ***\n\n", + "http_parser.c", HTTP_PARSER_ERRNO_LINE(parser), + http_errno_description(HTTP_PARSER_ERRNO(parser))); + + int this_line = 0, char_len = 0; + size_t i, j, len = strlen(raw), error_location_line = 0; + for (i = 0; i < len; i++) { + if (i == error_location) this_line = 1; + switch (raw[i]) { + case '\r': + char_len = 2; + fprintf(stderr, "\\r"); + break; + + case '\n': + char_len = 2; + fprintf(stderr, "\\n\n"); + + if (this_line) goto print; + + error_location_line = 0; + continue; + + default: + char_len = 1; + fputc(raw[i], stderr); + break; + } + if (!this_line) error_location_line += char_len; + } + + fprintf(stderr, "[eof]\n"); + + print: + for (j = 0; j < error_location_line; j++) { + fputc(' ', stderr); + } + fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); +} + +void +test_preserve_data (void) +{ + char my_data[] = "application-specific data"; + http_parser parser; + parser.data = my_data; + http_parser_init(&parser, HTTP_REQUEST); + if (parser.data != my_data) { + printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); + exit(1); + } +} + +struct url_test { + const char *name; + const char *url; + int is_connect; + struct http_parser_url u; + int rv; +}; + +const struct url_test url_tests[] = +{ {.name="proxy request" + ,.url="http://hostname/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 15, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + } + } + ,.rv=0 + } + +, {.name="CONNECT request" + ,.url="hostname:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 8 } /* UF_HOST */ + ,{ 9, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + } + } + ,.rv=0 + } + +, {.name="proxy ipv6 request" + ,.url="http://[1:2::3:4]/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 17, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + } + } + ,.rv=0 + } + +, {.name="CONNECT ipv6 address" + ,.url="[1:2::3:4]:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 1, 8 } /* UF_HOST */ + ,{ 11, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + } + } + ,.rv=0 + } + +, {.name="extra ? in query string" + ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" + ,.is_connect=0 + ,.u= + {.field_set=(1<field_set, u->port); + for (i = 0; i < UF_MAX; i++) { + if ((u->field_set & (1 << i)) == 0) { + printf("\tfield_data[%u]: unset\n", i); + continue; + } + + memcpy(part, url + u->field_data[i].off, u->field_data[i].len); + part[u->field_data[i].len] = '\0'; + + printf("\tfield_data[%u]: off: %u len: %u part: \"%s\"\n", + i, + u->field_data[i].off, + u->field_data[i].len, + part); + } +} + +void +test_parse_url (void) +{ + struct http_parser_url u; + const struct url_test *test; + unsigned int i; + int rv; + + for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { + test = &url_tests[i]; + memset(&u, 0, sizeof(u)); + + rv = http_parser_parse_url(test->url, + strlen(test->url), + test->is_connect, + &u); + + if (test->rv == 0) { + if (rv != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + exit(1); + } + + if (memcmp(&u, &test->u, sizeof(u)) != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", + test->url, test->name); + + printf("target http_parser_url:\n"); + dump_url(test->url, &test->u); + printf("result http_parser_url:\n"); + dump_url(test->url, &u); + + exit(1); + } + } else { + /* test->rv != 0 */ + if (rv == 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + exit(1); + } + } + } +} + +void +test_message (const struct message *message) +{ + size_t raw_len = strlen(message->raw); + size_t msg1len; + for (msg1len = 0; msg1len < raw_len; msg1len++) { + parser_init(message->type); + + size_t read; + const char *msg1 = message->raw; + const char *msg2 = msg1 + msg1len; + size_t msg2len = raw_len - msg1len; + + if (msg1len) { + read = parse(msg1, msg1len); + + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg1 + read; + goto test; + } + + if (read != msg1len) { + print_error(msg1, read); + exit(1); + } + } + + + read = parse(msg2, msg2len); + + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg2 + read; + goto test; + } + + if (read != msg2len) { + print_error(msg2, read); + exit(1); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(message->raw, read); + exit(1); + } + + test: + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + exit(1); + } + + if(!message_eq(0, message)) exit(1); + + parser_free(); + } +} + +void +test_message_count_body (const struct message *message) +{ + parser_init(message->type); + + size_t read; + size_t l = strlen(message->raw); + size_t i, toread; + size_t chunk = 4024; + + for (i = 0; i < l; i+= chunk) { + toread = MIN(l-i, chunk); + read = parse_count_body(message->raw + i, toread); + if (read != toread) { + print_error(message->raw, read); + exit(1); + } + } + + + read = parse_count_body(NULL, 0); + if (read != 0) { + print_error(message->raw, read); + exit(1); + } + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + exit(1); + } + + if(!message_eq(0, message)) exit(1); + + parser_free(); +} + +void +test_simple (const char *buf, enum http_errno err_expected) +{ + parser_init(HTTP_REQUEST); + + size_t parsed; + int pass; + enum http_errno err; + + parsed = parse(buf, strlen(buf)); + pass = (parsed == strlen(buf)); + err = HTTP_PARSER_ERRNO(parser); + parsed = parse(NULL, 0); + pass &= (parsed == 0); + + parser_free(); + + /* In strict mode, allow us to pass with an unexpected HPE_STRICT as + * long as the caller isn't expecting success. + */ +#if HTTP_PARSER_STRICT + if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { +#else + if (err_expected != err) { +#endif + fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", + http_errno_name(err_expected), http_errno_name(err), buf); + exit(1); + } +} + +void +test_header_overflow_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "header-key: header-value\r\n"; + size_t buflen = strlen(buf); + + int i; + for (i = 0; i < 10000; i++) { + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + //fprintf(stderr, "error found on iter %d\n", i); + assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); + return; + } + } + + fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); + exit(1); +} + +static void +test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) +{ + http_parser parser; + http_parser_init(&parser, HTTP_RESPONSE); + http_parser_execute(&parser, &settings_null, buf, buflen); + + if (expect_ok) + assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); + else + assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); +} + +void +test_header_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Length: " #size "\r\n" \ + "\r\n" + const char a[] = X(18446744073709551614); /* 2^64-2 */ + const char b[] = X(18446744073709551615); /* 2^64-1 */ + const char c[] = X(18446744073709551616); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_chunk_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" \ + #size "\r\n" \ + "..." + const char a[] = X(FFFFFFFFFFFFFFFE); /* 2^64-2 */ + const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ + const char c[] = X(10000000000000000); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_no_overflow_long_body (int req, size_t length) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + size_t i; + char buf1[3000]; + size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %zu\r\n\r\n", + req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", length); + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) + goto err; + + for (i = 0; i < length; i++) { + char foo = 'a'; + parsed = http_parser_execute(&parser, &settings_null, &foo, 1); + if (parsed != 1) + goto err; + } + + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) goto err; + return; + + err: + fprintf(stderr, + "\n*** error in test_no_overflow_long_body %s of length %zu ***\n", + req ? "REQUEST" : "RESPONSE", + length); + exit(1); +} + +void +test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) +{ + int message_count = count_parsed_messages(3, r1, r2, r3); + + char total[ strlen(r1->raw) + + strlen(r2->raw) + + strlen(r3->raw) + + 1 + ]; + total[0] = '\0'; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + parser_init(r1->type); + + size_t read; + + read = parse(total, strlen(total)); + + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + goto test; + } + + if (read != strlen(total)) { + print_error(total, read); + exit(1); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(total, read); + exit(1); + } + +test: + + if (message_count != num_messages) { + fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); + exit(1); + } + + if (!message_eq(0, r1)) exit(1); + if (message_count > 1 && !message_eq(1, r2)) exit(1); + if (message_count > 2 && !message_eq(2, r3)) exit(1); + + parser_free(); +} + +/* SCAN through every possible breaking to make sure the + * parser can handle getting the content in any chunks that + * might come from the socket + */ +void +test_scan (const struct message *r1, const struct message *r2, const struct message *r3) +{ + char total[80*1024] = "\0"; + char buf1[80*1024] = "\0"; + char buf2[80*1024] = "\0"; + char buf3[80*1024] = "\0"; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + size_t read; + + int total_len = strlen(total); + + int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; + int ops = 0 ; + + size_t buf1_len, buf2_len, buf3_len; + int message_count = count_parsed_messages(3, r1, r2, r3); + + int i,j,type_both; + for (type_both = 0; type_both < 2; type_both ++ ) { + for (j = 2; j < total_len; j ++ ) { + for (i = 1; i < j; i ++ ) { + + if (ops % 1000 == 0) { + printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); + fflush(stdout); + } + ops += 1; + + parser_init(type_both ? HTTP_BOTH : r1->type); + + buf1_len = i; + strncpy(buf1, total, buf1_len); + buf1[buf1_len] = 0; + + buf2_len = j - i; + strncpy(buf2, total+i, buf2_len); + buf2[buf2_len] = 0; + + buf3_len = total_len - j; + strncpy(buf3, total+j, buf3_len); + buf3[buf3_len] = 0; + + read = parse(buf1, buf1_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len) { + print_error(buf1, read); + goto error; + } + + read += parse(buf2, buf2_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len) { + print_error(buf2, read); + goto error; + } + + read += parse(buf3, buf3_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len + buf3_len) { + print_error(buf3, read); + goto error; + } + + parse(NULL, 0); + +test: + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + } + + if (message_count != num_messages) { + fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", + message_count, num_messages); + goto error; + } + + if (!message_eq(0, r1)) { + fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); + goto error; + } + + if (message_count > 1 && !message_eq(1, r2)) { + fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); + goto error; + } + + if (message_count > 2 && !message_eq(2, r3)) { + fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); + goto error; + } + + parser_free(); + } + } + } + puts("\b\b\b\b100%"); + return; + + error: + fprintf(stderr, "i=%d j=%d\n", i, j); + fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); + fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); + fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); + exit(1); +} + +// user required to free the result +// string terminated by \0 +char * +create_large_chunked_message (int body_size_in_kb, const char* headers) +{ + int i; + size_t wrote = 0; + size_t headers_len = strlen(headers); + size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; + char * buf = malloc(bufsize); + + memcpy(buf, headers, headers_len); + wrote += headers_len; + + for (i = 0; i < body_size_in_kb; i++) { + // write 1kb chunk into the body. + memcpy(buf + wrote, "400\r\n", 5); + wrote += 5; + memset(buf + wrote, 'C', 1024); + wrote += 1024; + strcpy(buf + wrote, "\r\n"); + wrote += 2; + } + + memcpy(buf + wrote, "0\r\n\r\n", 6); + wrote += 6; + assert(wrote == bufsize); + + return buf; +} + +/* Verify that we can pause parsing at any of the bytes in the + * message and still get the result that we're expecting. */ +void +test_message_pause (const struct message *msg) +{ + char *buf = (char*) msg->raw; + size_t buflen = strlen(msg->raw); + size_t nread; + + parser_init(msg->type); + + do { + nread = parse_pause(buf, buflen); + + // We can only set the upgrade buffer once we've gotten our message + // completion callback. + if (messages[0].message_complete_cb_called && + msg->upgrade && + parser->upgrade) { + messages[0].upgrade = buf + nread; + goto test; + } + + if (nread < buflen) { + + // Not much do to if we failed a strict-mode check + if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { + parser_free(); + return; + } + + assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); + } + + buf += nread; + buflen -= nread; + http_parser_pause(parser, 0); + } while (buflen > 0); + + nread = parse_pause(NULL, 0); + assert (nread == 0); + +test: + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); + exit(1); + } + + if(!message_eq(0, msg)) exit(1); + + parser_free(); +} + +int +main (void) +{ + parser = NULL; + int i, j, k; + int request_count; + int response_count; + + printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); + + for (request_count = 0; requests[request_count].name; request_count++); + for (response_count = 0; responses[response_count].name; response_count++); + + //// API + test_preserve_data(); + test_parse_url(); + + //// OVERFLOW CONDITIONS + + test_header_overflow_error(HTTP_REQUEST); + test_no_overflow_long_body(HTTP_REQUEST, 1000); + test_no_overflow_long_body(HTTP_REQUEST, 100000); + + test_header_overflow_error(HTTP_RESPONSE); + test_no_overflow_long_body(HTTP_RESPONSE, 1000); + test_no_overflow_long_body(HTTP_RESPONSE, 100000); + + test_header_content_length_overflow_error(); + test_chunk_content_length_overflow_error(); + + //// RESPONSES + + for (i = 0; i < response_count; i++) { + test_message(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + test_message_pause(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + if (!responses[i].should_keep_alive) continue; + for (j = 0; j < response_count; j++) { + if (!responses[j].should_keep_alive) continue; + for (k = 0; k < response_count; k++) { + test_multiple3(&responses[i], &responses[j], &responses[k]); + } + } + } + + test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); + test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); + + // test very large chunked response + { + char * msg = create_large_chunked_message(31337, + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n"); + struct message large_chunked = + {.name= "large chunked" + ,.type= HTTP_RESPONSE + ,.raw= msg + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.num_headers= 2 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/plain" } + } + ,.body_size= 31337*1024 + }; + test_message_count_body(&large_chunked); + free(msg); + } + + + + printf("response scan 1/2 "); + test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] + , &responses[NO_BODY_HTTP10_KA_204] + , &responses[NO_REASON_PHRASE] + ); + + printf("response scan 2/2 "); + test_scan( &responses[BONJOUR_MADAME_FR] + , &responses[UNDERSTORE_HEADER_KEY] + , &responses[NO_CARRIAGE_RET] + ); + + puts("responses okay"); + + + /// REQUESTS + + test_simple("hello world", HPE_INVALID_METHOD); + test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); + + + test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + + // Well-formed but incomplete + test_simple("GET / HTTP/1.1\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 6\r\n" + "\r\n" + "fooba", + HPE_OK); + + static const char *all_methods[] = { + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "UNLOCK", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + 0 }; + const char **this_method; + for (this_method = all_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_OK); + } + + static const char *bad_methods[] = { + "C******", + "M****", + 0 }; + for (this_method = bad_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_UNKNOWN); + } + + const char *dumbfuck2 = + "GET / HTTP/1.1\r\n" + "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" + "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" + "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" + "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" + "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" + "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" + "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" + "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" + "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" + "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" + "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" + "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" + "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" + "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" + "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" + "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" + "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" + "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" + "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" + "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" + "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" + "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" + "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" + "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" + "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" + "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" + "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" + "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" + "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" + "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" + "\tRA==\r\n" + "\t-----END CERTIFICATE-----\r\n" + "\r\n"; + test_simple(dumbfuck2, HPE_OK); + +#if 0 + // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body + // until EOF. + // + // no content-length + // error if there is a body without content length + const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + "HELLO"; + test_simple(bad_get_no_headers_no_body, 0); +#endif + /* TODO sending junk and large headers gets rejected */ + + + /* check to make sure our predefined requests are okay */ + for (i = 0; requests[i].name; i++) { + test_message(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + test_message_pause(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + if (!requests[i].should_keep_alive) continue; + for (j = 0; j < request_count; j++) { + if (!requests[j].should_keep_alive) continue; + for (k = 0; k < request_count; k++) { + test_multiple3(&requests[i], &requests[j], &requests[k]); + } + } + } + + printf("request scan 1/4 "); + test_scan( &requests[GET_NO_HEADERS_NO_BODY] + , &requests[GET_ONE_HEADER_NO_BODY] + , &requests[GET_NO_HEADERS_NO_BODY] + ); + + printf("request scan 2/4 "); + test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] + , &requests[POST_IDENTITY_BODY_WORLD] + , &requests[GET_FUNKY_CONTENT_LENGTH] + ); + + printf("request scan 3/4 "); + test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] + , &requests[CHUNKED_W_TRAILING_HEADERS] + , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] + ); + + printf("request scan 4/4 "); + test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] + , &requests[PREFIX_NEWLINE_GET ] + , &requests[CONNECT_REQUEST] + ); + + puts("requests okay"); + + return 0; +} diff --git a/.gems/gems/http_parser.rb-0.6.0/http_parser.rb.gemspec b/.gems/gems/http_parser.rb-0.6.0/http_parser.rb.gemspec new file mode 100644 index 0000000..9a406f8 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/http_parser.rb.gemspec @@ -0,0 +1,28 @@ +Gem::Specification.new do |s| + s.name = "http_parser.rb" + s.version = "0.6.0" + s.summary = "Simple callback-based HTTP request/response parser" + s.description = "Ruby bindings to http://github.com/ry/http-parser and http://github.com/a2800276/http-parser.java" + + s.authors = ["Marc-Andre Cournoyer", "Aman Gupta"] + s.email = ["macournoyer@gmail.com", "aman@tmm1.net"] + s.license = 'MIT' + + s.homepage = "http://github.com/tmm1/http_parser.rb" + s.files = `git ls-files`.split("\n") + Dir['ext/ruby_http_parser/vendor/**/*'] + + s.require_paths = ["lib"] + s.extensions = ["ext/ruby_http_parser/extconf.rb"] + + s.add_development_dependency 'rake-compiler', '>= 0.7.9' + s.add_development_dependency 'rspec', '>= 2.0.1' + s.add_development_dependency 'json', '>= 1.4.6' + s.add_development_dependency 'benchmark_suite' + s.add_development_dependency 'ffi' + + if RUBY_PLATFORM =~ /java/ + s.add_development_dependency 'jruby-openssl' + else + s.add_development_dependency 'yajl-ruby', '>= 0.8.1' + end +end diff --git a/.gems/gems/http_parser.rb-0.6.0/lib/http/parser.rb b/.gems/gems/http_parser.rb-0.6.0/lib/http/parser.rb new file mode 100644 index 0000000..4881b03 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/lib/http/parser.rb @@ -0,0 +1 @@ +require 'http_parser' diff --git a/.gems/gems/http_parser.rb-0.6.0/lib/http_parser.rb b/.gems/gems/http_parser.rb-0.6.0/lib/http_parser.rb new file mode 100644 index 0000000..c69f7a0 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/lib/http_parser.rb @@ -0,0 +1,21 @@ +$:.unshift File.expand_path('../', __FILE__) +require 'ruby_http_parser' + +Http = HTTP + +module HTTP + class Parser + class << self + attr_reader :default_header_value_type + + def default_header_value_type=(val) + if (val != :mixed && val != :strings && val != :arrays) + raise ArgumentError, "Invalid header value type" + end + @default_header_value_type = val + end + end + end +end + +HTTP::Parser.default_header_value_type = :mixed diff --git a/.gems/gems/http_parser.rb-0.6.0/spec/parser_spec.rb b/.gems/gems/http_parser.rb-0.6.0/spec/parser_spec.rb new file mode 100644 index 0000000..7134476 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/spec/parser_spec.rb @@ -0,0 +1,350 @@ +require "spec_helper" +require "json" + +describe HTTP::Parser do + before do + @parser = HTTP::Parser.new + + @headers = nil + @body = "" + @started = false + @done = false + + @parser.on_message_begin = proc{ @started = true } + @parser.on_headers_complete = proc { |e| @headers = e } + @parser.on_body = proc { |chunk| @body << chunk } + @parser.on_message_complete = proc{ @done = true } + end + + it "should have initial state" do + @parser.headers.should be_nil + + @parser.http_version.should be_nil + @parser.http_method.should be_nil + @parser.status_code.should be_nil + + @parser.request_url.should be_nil + + @parser.header_value_type.should == :mixed + end + + it "should allow us to set the header value type" do + [:mixed, :arrays, :strings].each do |type| + @parser.header_value_type = type + @parser.header_value_type.should == type + + parser_tmp = HTTP::Parser.new(nil, type) + parser_tmp.header_value_type.should == type + end + end + + it "should allow us to set the default header value type" do + [:mixed, :arrays, :strings].each do |type| + HTTP::Parser.default_header_value_type = type + + parser = HTTP::Parser.new + parser.header_value_type.should == type + end + end + + it "should throw an Argument Error if header value type is invalid" do + proc{ @parser.header_value_type = 'bob' }.should raise_error(ArgumentError) + end + + it "should throw an Argument Error if default header value type is invalid" do + proc{ HTTP::Parser.default_header_value_type = 'bob' }.should raise_error(ArgumentError) + end + + it "should implement basic api" do + @parser << + "GET /test?ok=1 HTTP/1.1\r\n" + + "User-Agent: curl/7.18.0\r\n" + + "Host: 0.0.0.0:5000\r\n" + + "Accept: */*\r\n" + + "Content-Length: 5\r\n" + + "\r\n" + + "World" + + @started.should be_true + @done.should be_true + + @parser.http_major.should == 1 + @parser.http_minor.should == 1 + @parser.http_version.should == [1,1] + @parser.http_method.should == 'GET' + @parser.status_code.should be_nil + + @parser.request_url.should == '/test?ok=1' + + @parser.headers.should == @headers + @parser.headers['User-Agent'].should == 'curl/7.18.0' + @parser.headers['Host'].should == '0.0.0.0:5000' + + @body.should == "World" + end + + it "should raise errors on invalid data" do + proc{ @parser << "BLAH" }.should raise_error(HTTP::Parser::Error) + end + + it "should abort parser via callback" do + @parser.on_headers_complete = proc { |e| @headers = e; :stop } + + data = + "GET / HTTP/1.0\r\n" + + "Content-Length: 5\r\n" + + "\r\n" + + "World" + + bytes = @parser << data + + bytes.should == 37 + data[bytes..-1].should == 'World' + + @headers.should == {'Content-Length' => '5'} + @body.should be_empty + @done.should be_false + end + + it "should reset to initial state" do + @parser << "GET / HTTP/1.0\r\n\r\n" + + @parser.http_method.should == 'GET' + @parser.http_version.should == [1,0] + + @parser.request_url.should == '/' + + @parser.reset!.should be_true + + @parser.http_version.should be_nil + @parser.http_method.should be_nil + @parser.status_code.should be_nil + + @parser.request_url.should be_nil + end + + it "should optionally reset parser state on no-body responses" do + @parser.reset!.should be_true + + @head, @complete = 0, 0 + @parser.on_headers_complete = proc {|h| @head += 1; :reset } + @parser.on_message_complete = proc { @complete += 1 } + @parser.on_body = proc {|b| fail } + + head_response = "HTTP/1.1 200 OK\r\nContent-Length:10\r\n\r\n" + + @parser << head_response + @head.should == 1 + @complete.should == 1 + + @parser << head_response + @head.should == 2 + @complete.should == 2 + end + + it "should retain callbacks after reset" do + @parser.reset!.should be_true + + @parser << "GET / HTTP/1.0\r\n\r\n" + @started.should be_true + @headers.should == {} + @done.should be_true + end + + it "should parse headers incrementally" do + request = + "GET / HTTP/1.0\r\n" + + "Header1: value 1\r\n" + + "Header2: value 2\r\n" + + "\r\n" + + while chunk = request.slice!(0,2) and !chunk.empty? + @parser << chunk + end + + @parser.headers.should == { + 'Header1' => 'value 1', + 'Header2' => 'value 2' + } + end + + it "should handle multiple headers using strings" do + @parser.header_value_type = :strings + + @parser << + "GET / HTTP/1.0\r\n" + + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + + "\r\n" + + @parser.headers["Set-Cookie"].should == "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com, NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" + end + + it "should handle multiple headers using strings" do + @parser.header_value_type = :arrays + + @parser << + "GET / HTTP/1.0\r\n" + + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + + "\r\n" + + @parser.headers["Set-Cookie"].should == [ + "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com", + "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" + ] + end + + it "should handle multiple headers using mixed" do + @parser.header_value_type = :mixed + + @parser << + "GET / HTTP/1.0\r\n" + + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + + "\r\n" + + @parser.headers["Set-Cookie"].should == [ + "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com", + "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" + ] + end + + it "should handle a single cookie using mixed" do + @parser.header_value_type = :mixed + + @parser << + "GET / HTTP/1.0\r\n" + + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + + "\r\n" + + @parser.headers["Set-Cookie"].should == "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com" + end + + it "should support alternative api" do + callbacks = double('callbacks') + callbacks.stub(:on_message_begin){ @started = true } + callbacks.stub(:on_headers_complete){ |e| @headers = e } + callbacks.stub(:on_body){ |chunk| @body << chunk } + callbacks.stub(:on_message_complete){ @done = true } + + @parser = HTTP::Parser.new(callbacks) + @parser << "GET / HTTP/1.0\r\n\r\n" + + @started.should be_true + @headers.should == {} + @body.should == '' + @done.should be_true + end + + it "should ignore extra content beyond specified length" do + @parser << + "GET / HTTP/1.0\r\n" + + "Content-Length: 5\r\n" + + "\r\n" + + "hello" + + " \n" + + @body.should == 'hello' + @done.should be_true + end + + it 'sets upgrade_data if available' do + @parser << + "GET /demo HTTP/1.1\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: WebSocket\r\n\r\n" + + "third key data" + + @parser.upgrade?.should be_true + @parser.upgrade_data.should == 'third key data' + end + + it 'sets upgrade_data to blank if un-available' do + @parser << + "GET /demo HTTP/1.1\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: WebSocket\r\n\r\n" + + @parser.upgrade?.should be_true + @parser.upgrade_data.should == '' + end + + it 'should stop parsing headers when instructed' do + request = "GET /websocket HTTP/1.1\r\n" + + "host: localhost\r\n" + + "connection: Upgrade\r\n" + + "upgrade: websocket\r\n" + + "sec-websocket-key: SD6/hpYbKjQ6Sown7pBbWQ==\r\n" + + "sec-websocket-version: 13\r\n" + + "\r\n" + + @parser.on_headers_complete = proc { |e| :stop } + offset = (@parser << request) + @parser.upgrade?.should be_true + @parser.upgrade_data.should == '' + offset.should == request.length + end + + it "should execute on_body on requests with no content-length" do + @parser.reset!.should be_true + + @head, @complete, @body = 0, 0, 0 + @parser.on_headers_complete = proc {|h| @head += 1 } + @parser.on_message_complete = proc { @complete += 1 } + @parser.on_body = proc {|b| @body += 1 } + + head_response = "HTTP/1.1 200 OK\r\n\r\nstuff" + + @parser << head_response + @parser << '' + @head.should == 1 + @complete.should == 1 + @body.should == 1 + end + + + %w[ request response ].each do |type| + JSON.parse(File.read(File.expand_path("../support/#{type}s.json", __FILE__))).each do |test| + test['headers'] ||= {} + next if !defined?(JRUBY_VERSION) and HTTP::Parser.strict? != test['strict'] + + it "should parse #{type}: #{test['name']}" do + @parser << test['raw'] + + @parser.http_method.should == test['method'] + @parser.keep_alive?.should == test['should_keep_alive'] + + if test.has_key?('upgrade') and test['upgrade'] != 0 + @parser.upgrade?.should be_true + @parser.upgrade_data.should == test['upgrade'] + end + + fields = %w[ + http_major + http_minor + ] + + if test['type'] == 'HTTP_REQUEST' + fields += %w[ + request_url + ] + else + fields += %w[ + status_code + ] + end + + fields.each do |field| + @parser.send(field).should == test[field] + end + + @headers.size.should == test['num_headers'] + @headers.should == test['headers'] + + @body.should == test['body'] + @body.size.should == test['body_size'] if test['body_size'] + end + end + end +end diff --git a/.gems/gems/http_parser.rb-0.6.0/spec/spec_helper.rb b/.gems/gems/http_parser.rb-0.6.0/spec/spec_helper.rb new file mode 100644 index 0000000..a4295f9 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/spec/spec_helper.rb @@ -0,0 +1 @@ +require "http_parser" diff --git a/.gems/gems/http_parser.rb-0.6.0/spec/support/requests.json b/.gems/gems/http_parser.rb-0.6.0/spec/support/requests.json new file mode 100644 index 0000000..dbb6e98 --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/spec/support/requests.json @@ -0,0 +1,612 @@ +[ + { + "name": "curl get", + "type": "HTTP_REQUEST", + "raw": "GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/test", + "request_url": "/test", + "num_headers": 3, + "headers": { + "User-Agent": "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1", + "Host": "0.0.0.0=5000", + "Accept": "*/*" + }, + "body": "", + "strict": true + }, + { + "name": "firefox get", + "type": "HTTP_REQUEST", + "raw": "GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/favicon.ico", + "request_url": "/favicon.ico", + "num_headers": 8, + "headers": { + "Host": "0.0.0.0=5000", + "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive": "300", + "Connection": "keep-alive" + }, + "body": "", + "strict": true + }, + { + "name": "dumbfuck", + "type": "HTTP_REQUEST", + "raw": "GET /dumbfuck HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/dumbfuck", + "request_url": "/dumbfuck", + "num_headers": 1, + "headers": { + "aaaaaaaaaaaaa": "++++++++++" + }, + "body": "", + "strict": true + }, + { + "name": "fragment in url", + "type": "HTTP_REQUEST", + "raw": "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "page=1", + "fragment": "posts-17408", + "request_path": "/forums/1/topics/2375", + "request_url": "/forums/1/topics/2375?page=1#posts-17408", + "num_headers": 0, + "body": "", + "strict": true + }, + { + "name": "get no headers no body", + "type": "HTTP_REQUEST", + "raw": "GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/get_no_headers_no_body/world", + "request_url": "/get_no_headers_no_body/world", + "num_headers": 0, + "body": "", + "strict": true + }, + { + "name": "get one header no body", + "type": "HTTP_REQUEST", + "raw": "GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/get_one_header_no_body", + "request_url": "/get_one_header_no_body", + "num_headers": 1, + "headers": { + "Accept": "*/*" + }, + "body": "", + "strict": true + }, + { + "name": "get funky content length body hello", + "type": "HTTP_REQUEST", + "raw": "GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/get_funky_content_length_body_hello", + "request_url": "/get_funky_content_length_body_hello", + "num_headers": 1, + "headers": { + "conTENT-Length": "5" + }, + "body": "HELLO", + "strict": true + }, + { + "name": "post identity body world", + "type": "HTTP_REQUEST", + "raw": "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "POST", + "query_string": "q=search", + "fragment": "hey", + "request_path": "/post_identity_body_world", + "request_url": "/post_identity_body_world?q=search#hey", + "num_headers": 3, + "headers": { + "Accept": "*/*", + "Transfer-Encoding": "identity", + "Content-Length": "5" + }, + "body": "World", + "strict": true + }, + { + "name": "post - chunked body: all your base are belong to us", + "type": "HTTP_REQUEST", + "raw": "POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "POST", + "query_string": "", + "fragment": "", + "request_path": "/post_chunked_all_your_base", + "request_url": "/post_chunked_all_your_base", + "num_headers": 1, + "headers": { + "Transfer-Encoding": "chunked" + }, + "body": "all your base are belong to us", + "strict": true + }, + { + "name": "two chunks ; triple zero ending", + "type": "HTTP_REQUEST", + "raw": "POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "POST", + "query_string": "", + "fragment": "", + "request_path": "/two_chunks_mult_zero_end", + "request_url": "/two_chunks_mult_zero_end", + "num_headers": 1, + "headers": { + "Transfer-Encoding": "chunked" + }, + "body": "hello world", + "strict": true + }, + { + "name": "chunked with trailing headers. blech.", + "type": "HTTP_REQUEST", + "raw": "POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "POST", + "query_string": "", + "fragment": "", + "request_path": "/chunked_w_trailing_headers", + "request_url": "/chunked_w_trailing_headers", + "num_headers": 3, + "headers": { + "Transfer-Encoding": "chunked", + "Vary": "*", + "Content-Type": "text/plain" + }, + "body": "hello world", + "strict": true + }, + { + "name": "with bullshit after the length", + "type": "HTTP_REQUEST", + "raw": "POST /chunked_w_bullshit_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n6; blahblah; blah\r\n world\r\n0\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "POST", + "query_string": "", + "fragment": "", + "request_path": "/chunked_w_bullshit_after_length", + "request_url": "/chunked_w_bullshit_after_length", + "num_headers": 1, + "headers": { + "Transfer-Encoding": "chunked" + }, + "body": "hello world", + "strict": true + }, + { + "name": "with quotes", + "type": "HTTP_REQUEST", + "raw": "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "foo=\"bar\"", + "fragment": "", + "request_path": "/with_\"stupid\"_quotes", + "request_url": "/with_\"stupid\"_quotes?foo=\"bar\"", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "apachebench get", + "type": "HTTP_REQUEST", + "raw": "GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/test", + "request_url": "/test", + "num_headers": 3, + "headers": { + "Host": "0.0.0.0:5000", + "User-Agent": "ApacheBench/2.3", + "Accept": "*/*" + }, + "body": "", + "strict": true + }, + { + "name": "query url with question mark", + "type": "HTTP_REQUEST", + "raw": "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "foo=bar?baz", + "fragment": "", + "request_path": "/test.cgi", + "request_url": "/test.cgi?foo=bar?baz", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "newline prefix get", + "type": "HTTP_REQUEST", + "raw": "\r\nGET /test HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/test", + "request_url": "/test", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "upgrade request", + "type": "HTTP_REQUEST", + "raw": "GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/demo", + "request_url": "/demo", + "num_headers": 7, + "upgrade": "Hot diggity dogg", + "headers": { + "Host": "example.com", + "Connection": "Upgrade", + "Sec-WebSocket-Key2": "12998 5 Y3 1 .P00", + "Sec-WebSocket-Protocol": "sample", + "Upgrade": "WebSocket", + "Sec-WebSocket-Key1": "4 @1 46546xW%0l 1 5", + "Origin": "http://example.com" + }, + "body": "", + "strict": true + }, + { + "name": "connect request", + "type": "HTTP_REQUEST", + "raw": "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\r\nand yet even more data", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "method": "CONNECT", + "query_string": "", + "fragment": "", + "request_path": "", + "request_url": "0-home0.netscape.com:443", + "num_headers": 2, + "upgrade": "some data\r\nand yet even more data", + "headers": { + "User-agent": "Mozilla/1.1N", + "Proxy-authorization": "basic aGVsbG86d29ybGQ=" + }, + "body": "", + "strict": true + }, + { + "name": "report request", + "type": "HTTP_REQUEST", + "raw": "REPORT /test HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "REPORT", + "query_string": "", + "fragment": "", + "request_path": "/test", + "request_url": "/test", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "request with no http version", + "type": "HTTP_REQUEST", + "raw": "GET /\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 0, + "http_minor": 9, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/", + "request_url": "/", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "m-search request", + "type": "HTTP_REQUEST", + "raw": "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "M-SEARCH", + "query_string": "", + "fragment": "", + "request_path": "*", + "request_url": "*", + "num_headers": 3, + "headers": { + "HOST": "239.255.255.250:1900", + "MAN": "\"ssdp:discover\"", + "ST": "\"ssdp:all\"" + }, + "body": "", + "strict": true + }, + { + "name": "line folding in header value", + "type": "HTTP_REQUEST", + "raw": "GET / HTTP/1.1\r\nLine1: abc\r\n\tdef\r\n ghi\r\n\t\tjkl\r\n mno \r\n\t \tqrs\r\nLine2: \t line2\t\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "/", + "request_url": "/", + "num_headers": 2, + "headers": { + "Line1": "abcdefghijklmno qrs", + "Line2": "line2\t" + }, + "body": "", + "strict": true + }, + { + "name": "host terminated by a query string", + "type": "HTTP_REQUEST", + "raw": "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "hail=all", + "fragment": "", + "request_path": "", + "request_url": "http://hypnotoad.org?hail=all", + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "host:port terminated by a query string", + "type": "HTTP_REQUEST", + "raw": "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "hail=all", + "fragment": "", + "request_path": "", + "request_url": "http://hypnotoad.org:1234?hail=all", + "port": 1234, + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "host:port terminated by a space", + "type": "HTTP_REQUEST", + "raw": "GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "", + "fragment": "", + "request_path": "", + "request_url": "http://hypnotoad.org:1234", + "port": 1234, + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "PATCH request", + "type": "HTTP_REQUEST", + "raw": "PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "PATCH", + "query_string": "", + "fragment": "", + "request_path": "/file.txt", + "request_url": "/file.txt", + "num_headers": 4, + "headers": { + "Host": "www.example.com", + "Content-Type": "application/example", + "If-Match": "\"e0023aa4e\"", + "Content-Length": "10" + }, + "body": "cccccccccc", + "strict": true + }, + { + "name": "connect caps request", + "type": "HTTP_REQUEST", + "raw": "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "method": "CONNECT", + "query_string": "", + "fragment": "", + "request_path": "", + "request_url": "HOME0.NETSCAPE.COM:443", + "num_headers": 2, + "upgrade": "", + "headers": { + "User-agent": "Mozilla/1.1N", + "Proxy-authorization": "basic aGVsbG86d29ybGQ=" + }, + "body": "", + "strict": true + }, + { + "name": "utf-8 path request", + "type": "HTTP_REQUEST", + "strict": false, + "raw": "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "method": "GET", + "query_string": "q=1", + "fragment": "narf", + "request_path": "/δ¶/δt/pope", + "request_url": "/δ¶/δt/pope?q=1#narf", + "num_headers": 1, + "headers": { + "Host": "github.com" + }, + "body": "" + }, + { + "name": "hostname underscore", + "type": "HTTP_REQUEST", + "strict": false, + "raw": "CONNECT home_0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "method": "CONNECT", + "query_string": "", + "fragment": "", + "request_path": "", + "request_url": "home_0.netscape.com:443", + "num_headers": 2, + "upgrade": "", + "headers": { + "User-agent": "Mozilla/1.1N", + "Proxy-authorization": "basic aGVsbG86d29ybGQ=" + }, + "body": "" + } +] \ No newline at end of file diff --git a/.gems/gems/http_parser.rb-0.6.0/spec/support/responses.json b/.gems/gems/http_parser.rb-0.6.0/spec/support/responses.json new file mode 100644 index 0000000..6dde20b --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/spec/support/responses.json @@ -0,0 +1,375 @@ +[ + { + "name": "google 301", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219 \r\n\r\n\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 301, + "num_headers": 8, + "headers": { + "Location": "http://www.google.com/", + "Content-Type": "text/html; charset=UTF-8", + "Date": "Sun, 26 Apr 2009 11:11:49 GMT", + "Expires": "Tue, 26 May 2009 11:11:49 GMT", + "X-$PrototypeBI-Version": "1.6.0.3", + "Cache-Control": "public, max-age=2592000", + "Server": "gws", + "Content-Length": "219 " + }, + "body": "\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n", + "strict": true + }, + { + "name": "no content-length response", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 5, + "headers": { + "Date": "Tue, 04 Aug 2009 07:59:32 GMT", + "Server": "Apache", + "X-Powered-By": "Servlet/2.5 JSP/2.1", + "Content-Type": "text/xml; charset=utf-8", + "Connection": "close" + }, + "body": "\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n", + "strict": true + }, + { + "name": "404 no headers no body", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 404 Not Found\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 404, + "num_headers": 0, + "headers": { + + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "301 no response phrase", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 301\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 301, + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "200 trailing space on chunked body", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 2, + "headers": { + "Content-Type": "text/plain", + "Transfer-Encoding": "chunked" + }, + "body_size": 65, + "body": "This is the data in the first chunk\r\nand this is the second one\r\n", + "strict": true + }, + { + "name": "no carriage ret", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 2, + "headers": { + "Content-Type": "text/html; charset=utf-8", + "Connection": "close" + }, + "body": "these headers are from http://news.ycombinator.com/", + "strict": true + }, + { + "name": "proxy connection", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 4, + "headers": { + "Content-Type": "text/html; charset=UTF-8", + "Content-Length": "11", + "Proxy-Connection": "close", + "Date": "Thu, 31 Dec 2009 20:55:48 +0000" + }, + "body": "hello world", + "strict": true + }, + { + "name": "underscore header key", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 4, + "headers": { + "Server": "DCLK-AdSvr", + "Content-Type": "text/xml", + "Content-Length": "0", + "DCLK_imp": "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" + }, + "body": "", + "strict": true + }, + { + "name": "bonjourmadame.fr", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "status_code": 301, + "num_headers": 9, + "headers": { + "Date": "Thu, 03 Jun 2010 09:56:32 GMT", + "Server": "Apache/2.2.3 (Red Hat)", + "Cache-Control": "public", + "Pragma": "", + "Location": "http://www.bonjourmadame.fr/", + "Vary": "Accept-Encoding", + "Content-Length": "0", + "Content-Type": "text/html; charset=UTF-8", + "Connection": "keep-alive" + }, + "body": "", + "strict": true + }, + { + "name": "field underscore", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 11, + "headers": { + "Date": "Tue, 28 Sep 2010 01:14:13 GMT", + "Server": "Apache", + "Cache-Control": "no-cache, must-revalidate", + "Expires": "Mon, 26 Jul 1997 05:00:00 GMT", + ".et-Cookie": "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com", + "Vary": "Accept-Encoding", + "_eep-Alive": "timeout=45", + "_onnection": "Keep-Alive", + "Transfer-Encoding": "chunked", + "Content-Type": "text/html", + "Connection": "close" + }, + "body": "", + "strict": true + }, + { + "name": "non-ASCII in status line", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 500 Oriëntatieprobleem\r\nDate: Fri, 5 Nov 2010 23:07:12 GMT+2\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 500, + "num_headers": 3, + "headers": { + "Date": "Fri, 5 Nov 2010 23:07:12 GMT+2", + "Content-Length": "0", + "Connection": "close" + }, + "body": "", + "strict": true + }, + { + "name": "http version 0.9", + "type": "HTTP_RESPONSE", + "raw": "HTTP/0.9 200 OK\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 0, + "http_minor": 9, + "status_code": 200, + "num_headers": 0, + "headers": { + + }, + "body": "", + "strict": true + }, + { + "name": "neither content-length nor transfer-encoding response", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 1, + "headers": { + "Content-Type": "text/plain" + }, + "body": "hello world", + "strict": true + }, + { + "name": "HTTP/1.0 with keep-alive and EOF-terminated 200 status", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 0, + "status_code": 200, + "num_headers": 1, + "headers": { + "Connection": "keep-alive" + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "HTTP/1.0 with keep-alive and a 204 status", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 0, + "status_code": 204, + "num_headers": 1, + "headers": { + "Connection": "keep-alive" + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "HTTP/1.1 with an EOF-terminated 200 status", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": true, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 0, + "headers": { + + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "HTTP/1.1 with a 204 status", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 204 No content\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 204, + "num_headers": 0, + "headers": { + + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "HTTP/1.1 with a 204 status and keep-alive disabled", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 204 No content\r\nConnection: close\r\n\r\n", + "should_keep_alive": false, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 204, + "num_headers": 1, + "headers": { + "Connection": "close" + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "HTTP/1.1 with chunked endocing and a 200 response", + "type": "HTTP_RESPONSE", + "raw": "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 1, + "headers": { + "Transfer-Encoding": "chunked" + }, + "body_size": 0, + "body": "", + "strict": true + }, + { + "name": "field space", + "type": "HTTP_RESPONSE", + "strict": false, + "raw": "HTTP/1.1 200 OK\r\nServer: Microsoft-IIS/6.0\r\nX-Powered-By: ASP.NET\r\nen-US Content-Type: text/xml\r\nContent-Type: text/xml\r\nContent-Length: 16\r\nDate: Fri, 23 Jul 2010 18:45:38 GMT\r\nConnection: keep-alive\r\n\r\nhello", + "should_keep_alive": true, + "message_complete_on_eof": false, + "http_major": 1, + "http_minor": 1, + "status_code": 200, + "num_headers": 7, + "headers": { + "Server": "Microsoft-IIS/6.0", + "X-Powered-By": "ASP.NET", + "en-US Content-Type": "text/xml", + "Content-Type": "text/xml", + "Content-Length": "16", + "Date": "Fri, 23 Jul 2010 18:45:38 GMT", + "Connection": "keep-alive" + }, + "body": "hello" + } +] \ No newline at end of file diff --git a/.gems/gems/http_parser.rb-0.6.0/tasks/compile.rake b/.gems/gems/http_parser.rb-0.6.0/tasks/compile.rake new file mode 100644 index 0000000..22d6f6d --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/tasks/compile.rake @@ -0,0 +1,42 @@ +require 'rubygems/package_task' +require 'rake/extensiontask' +require 'rake/javaextensiontask' + +def gemspec + @clean_gemspec ||= eval(File.read(File.expand_path('../../http_parser.rb.gemspec', __FILE__))) +end + +Gem::PackageTask.new(gemspec) do |pkg| +end + +if RUBY_PLATFORM =~ /java/ + Rake::JavaExtensionTask.new("ruby_http_parser", gemspec) do |ext| + ext.classpath = File.expand_path('../../ext/ruby_http_parser/vendor/http-parser-java/ext/primitives.jar', __FILE__) + end +else + Rake::ExtensionTask.new("ruby_http_parser", gemspec) do |ext| + unless RUBY_PLATFORM =~ /mswin|mingw/ + ext.cross_compile = true + ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60'] + + # inject 1.8/1.9 pure-ruby entry point + ext.cross_compiling do |spec| + spec.files += ['lib/ruby_http_parser.rb'] + end + end + end +end + +file 'lib/ruby_http_parser.rb' do |t| + File.open(t.name, 'wb') do |f| + f.write <<-eoruby +RUBY_VERSION =~ /(\\d+.\\d+)/ +require "\#{$1}/ruby_http_parser" + eoruby + end + at_exit{ FileUtils.rm t.name if File.exists?(t.name) } +end + +if Rake::Task.task_defined?(:cross) + task :cross => 'lib/ruby_http_parser.rb' +end diff --git a/.gems/gems/http_parser.rb-0.6.0/tasks/fixtures.rake b/.gems/gems/http_parser.rb-0.6.0/tasks/fixtures.rake new file mode 100644 index 0000000..b5d36ef --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/tasks/fixtures.rake @@ -0,0 +1,71 @@ +desc "Generate test fixtures" +task :fixtures => :submodules do + require 'yajl' + data = File.read File.expand_path('../../ext/ruby_http_parser/vendor/http-parser/test.c', __FILE__) + + %w[ requests responses ].each do |type| + # find test definitions in between requests/responses[]= and .name=NULL + tmp = data[/#{type}\[\]\s*=(.+?),\s*\{\s*\.name=\s*NULL/m, 1] + + # replace first { with a [ (parsing an array of test cases) + tmp.sub!('{','[') + + # replace booleans + tmp.gsub!('TRUE', 'true') + tmp.gsub!('FALSE', 'false') + + # mark strict mode tests + tmp.gsub!(%r|#if\s+!HTTP_PARSER_STRICT(.+?)#endif\s*/\*\s*!HTTP_PARSER_STRICT.+\n|m){ + $1.gsub(/^(.+,\.type= .+)$/, "\\1\n, .strict= false") + } + + # remove macros and comments + tmp.gsub!(/^#(if|elif|endif|define).+$/,'') + tmp.gsub!(/\/\*(.+?)\*\/$/,'') + + # HTTP_* enums become strings + tmp.gsub!(/(= )(HTTP_\w+)/){ + "#{$1}#{$2.sub('MSEARCH','M-SEARCH').dump}" + } + + # join multiline strings for body and raw data + tmp.gsub!(/((body|raw)\s*=)(.+?)(\n\s+[\},])/m){ + before, after = $1, $4 + raw = $3.split("\n").map{ |l| l.strip[1..-2] }.join('') + "#{before} \"#{raw}\" #{after}" + } + + # make headers an array of array tuples + tmp.gsub!(/(\.headers\s*=)(.+?)(\s*,\.)/m){ + before, after = $1, $3 + raw = $2.gsub('{', '[').gsub('}', ']') + "#{before} #{raw} #{after}" + } + + # .name= becomes "name": + tmp.gsub!(/^(.{2,5})\.(\w+)\s*=/){ + "#{$1}#{$2.dump}: " + } + + # evaluate addition expressions + tmp.gsub!(/(body_size\":\s*)(\d+)\+(\d+)/){ + "#{$1}#{$2.to_i+$3.to_i}" + } + + # end result array + tmp << ']' + + # normalize data + results = Yajl.load(tmp, :symbolize_keys => true) + results.map{ |res| + res[:headers] and res[:headers] = Hash[*res[:headers].flatten] + res[:method] and res[:method].gsub!(/^HTTP_/, '') + res[:strict] = true unless res.has_key?(:strict) + } + + # write to a file + File.open("spec/support/#{type}.json", 'w'){ |f| + f.write Yajl.dump(results, :pretty => true) + } + end +end diff --git a/.gems/gems/http_parser.rb-0.6.0/tasks/spec.rake b/.gems/gems/http_parser.rb-0.6.0/tasks/spec.rake new file mode 100644 index 0000000..8f5d9ea --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/tasks/spec.rake @@ -0,0 +1,5 @@ +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new do |t| + t.rspec_opts = %w(-fs -c) +end diff --git a/.gems/gems/http_parser.rb-0.6.0/tasks/submodules.rake b/.gems/gems/http_parser.rb-0.6.0/tasks/submodules.rake new file mode 100644 index 0000000..d978e9f --- /dev/null +++ b/.gems/gems/http_parser.rb-0.6.0/tasks/submodules.rake @@ -0,0 +1,7 @@ +desc "Fetch upstream submodules" +task :submodules do + if Dir['ext/ruby_http_parser/vendor/http-parser/*'].empty? + sh 'git submodule init' + sh 'git submodule update' + end +end diff --git a/.gems/gems/memoizable-0.4.2/CONTRIBUTING.md b/.gems/gems/memoizable-0.4.2/CONTRIBUTING.md new file mode 100644 index 0000000..333b403 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/CONTRIBUTING.md @@ -0,0 +1,11 @@ +Contributing +------------ + +* If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin. +* Fork the project. +* Make your feature addition or bug fix. + * Follow this [style guide](https://github.com/dkubb/styleguide). +* Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered. +* Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) +* Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged. +* Send me a pull request. Bonus points for topic branches. diff --git a/.gems/gems/memoizable-0.4.2/LICENSE.md b/.gems/gems/memoizable-0.4.2/LICENSE.md new file mode 100644 index 0000000..46a93e6 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2013 Dan Kubb, Erik Michaels-Ober + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/memoizable-0.4.2/README.md b/.gems/gems/memoizable-0.4.2/README.md new file mode 100644 index 0000000..2ff6ff9 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/README.md @@ -0,0 +1,111 @@ +# Memoizable + +[![Gem Version](http://img.shields.io/gem/v/memoizable.svg)][gem] +[![Build Status](http://img.shields.io/travis/dkubb/memoizable.svg)][travis] +[![Dependency Status](http://img.shields.io/gemnasium/dkubb/memoizable.svg)][gemnasium] +[![Code Climate](http://img.shields.io/codeclimate/github/dkubb/memoizable.svg)][codeclimate] +[![Coverage Status](http://img.shields.io/coveralls/dkubb/memoizable.svg)][coveralls] + +[gem]: https://rubygems.org/gems/memoizable +[travis]: https://travis-ci.org/dkubb/memoizable +[gemnasium]: https://gemnasium.com/dkubb/memoizable +[codeclimate]: https://codeclimate.com/github/dkubb/memoizable +[coveralls]: https://coveralls.io/r/dkubb/memoizable + +Memoize method return values + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for details. + +## Rationale + +Memoization is an optimization that saves the return value of a method so it +doesn't need to be re-computed every time that method is called. For example, +perhaps you've written a method like this: + +```ruby +class Planet + # This is the equation for the area of a sphere. If it's true for a + # particular instance of a planet, then that planet is spherical. + def spherical? + 4 * Math::PI * radius ** 2 == area + end +end +``` + +This code will re-compute whether a particular planet is spherical every time +the method is called. If the method is called more than once, it may be more +efficient to save the computed value in an instance variable, like so: + +```ruby +class Planet + def spherical? + @spherical ||= 4 * Math::PI * radius ** 2 == area + end +end +``` + +One problem with this approach is that, if the return value is `false`, the +value will still be computed each time the method is called. It also becomes +unweildy for methods that grow to be longer than one line. + +These problems can be solved by mixing-in the `Memoizable` module and memoizing +the method. + +```ruby +require 'memoizable' + +class Planet + include Memoizable + def spherical? + 4 * Math::PI * radius ** 2 == area + end + memoize :spherical? +end +``` + +## Warning + +The example above assumes that the radius and area of a planet will not change +over time. This seems like a reasonable assumption but such an assumption is +not safe in every domain. If it was possible for one of the attributes to +change between method calls, memoizing that value could produce the wrong +result. Please keep this in mind when considering which methods to memoize. + +Supported Ruby Versions +----------------------- + +This library aims to support and is [tested against][travis] the following Ruby +implementations: + +* Ruby 1.8.7 +* Ruby 1.9.2 +* Ruby 1.9.3 +* Ruby 2.0.0 +* Ruby 2.1.0 +* [JRuby][] +* [Rubinius][] +* [Ruby Enterprise Edition][ree] + +[jruby]: http://jruby.org/ +[rubinius]: http://rubini.us/ +[ree]: http://www.rubyenterpriseedition.com/ + +If something doesn't work on one of these versions, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby versions or +implementations, however support will only be provided for the implementations +listed above. + +If you would like this library to support another Ruby version or +implementation, you may volunteer to be a maintainer. Being a maintainer +entails making sure all tests run and pass on that implementation. When +something breaks on your implementation, you will be responsible for providing +patches in a timely fashion. If critical issues for a particular implementation +exist at the time of a major release, support for that Ruby version may be +dropped. + +## Copyright + +Copyright © 2013 Dan Kubb, Erik Michaels-Ober. See LICENSE for details. diff --git a/.gems/gems/memoizable-0.4.2/Rakefile b/.gems/gems/memoizable-0.4.2/Rakefile new file mode 100644 index 0000000..242da90 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/Rakefile @@ -0,0 +1,10 @@ +# encoding: utf-8 + +require 'bundler' +require 'rspec/core/rake_task' + +Bundler::GemHelper.install_tasks +RSpec::Core::RakeTask.new(:spec) + +task :test => :spec +task :default => :spec diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable.rb new file mode 100644 index 0000000..375e179 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable.rb @@ -0,0 +1,33 @@ +# encoding: utf-8 + +require 'monitor' +require 'thread_safe' + +require 'memoizable/instance_methods' +require 'memoizable/method_builder' +require 'memoizable/module_methods' +require 'memoizable/memory' +require 'memoizable/version' + +# Allow methods to be memoized +module Memoizable + include InstanceMethods + + # Default freezer + Freezer = lambda { |object| object.freeze }.freeze + + # Hook called when module is included + # + # @param [Module] descendant + # the module or class including Memoizable + # + # @return [self] + # + # @api private + def self.included(descendant) + super + descendant.extend(ModuleMethods) + end + private_class_method :included + +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable/instance_methods.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable/instance_methods.rb new file mode 100644 index 0000000..271387d --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable/instance_methods.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +module Memoizable + + # Methods mixed in to memoizable instances + module InstanceMethods + + # Freeze the object + # + # @example + # object.freeze # object is now frozen + # + # @return [Object] + # + # @api public + def freeze + memoized_method_cache # initialize method cache + super + end + + # Sets a memoized value for a method + # + # @example + # object.memoize(hash: 12345) + # + # @param [Hash{Symbol => Object}] data + # the data to memoize + # + # @return [self] + # + # @api public + def memoize(data) + data.each { |name, value| memoized_method_cache[name] = value } + self + end + + private + + # The memoized method results + # + # @return [Hash] + # + # @api private + def memoized_method_cache + @_memoized_method_cache ||= Memory.new + end + + end # InstanceMethods +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable/memory.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable/memory.rb new file mode 100644 index 0000000..4aa0d7a --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable/memory.rb @@ -0,0 +1,104 @@ +# encoding: utf-8 + +module Memoizable + + # Storage for memoized methods + class Memory + + # Initialize the memory storage for memoized methods + # + # @param [ThreadSafe::Cache] memory + # + # @return [undefined] + # + # @api private + def initialize + @memory = ThreadSafe::Cache.new + @monitor = Monitor.new + freeze + end + + # Get the value from memory + # + # @param [Symbol] name + # + # @return [Object] + # + # @api public + def [](name) + @memory.fetch(name) do + fail NameError, "No method #{name} is memoized" + end + end + + # Store the value in memory + # + # @param [Symbol] name + # @param [Object] value + # + # @return [undefined] + # + # @api public + def []=(name, value) + memoized = true + @memory.compute_if_absent(name) do + memoized = false + value + end + fail ArgumentError, "The method #{name} is already memoized" if memoized + end + + # Fetch the value from memory, or store it if it does not exist + # + # @param [Symbol] name + # + # @yieldreturn [Object] + # the value to memoize + # + # @api public + def fetch(name) + @memory.fetch(name) do # check for the key + @monitor.synchronize do # acquire a lock if the key is not found + @memory.fetch(name) do # recheck under lock + self[name] = yield # set the value + end + end + end + end + + # Test if the name has a value in memory + # + # @param [Symbol] name + # + # @return [Boolean] + # + # @api public + def key?(name) + @memory.key?(name) + end + + # A hook that allows Marshal to dump the object + # + # @return [Hash] + # A hash used to populate the internal memory + # + # @api public + def marshal_dump + @memory.marshal_dump + end + + # A hook that allows Marshal to load the object + # + # @param [Hash] hash + # A hash used to populate the internal memory + # + # @return [undefined] + # + # @api public + def marshal_load(hash) + initialize + @memory.marshal_load(hash) + end + + end # Memory +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable/method_builder.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable/method_builder.rb new file mode 100644 index 0000000..d41483f --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable/method_builder.rb @@ -0,0 +1,145 @@ +# encoding: utf-8 + +module Memoizable + + # Build the memoized method + class MethodBuilder + + # Raised when the method arity is invalid + class InvalidArityError < ArgumentError + + # Initialize an invalid arity exception + # + # @param [Module] descendant + # @param [Symbol] method + # @param [Integer] arity + # + # @api private + def initialize(descendant, method, arity) + super("Cannot memoize #{descendant}##{method}, its arity is #{arity}") + end + + end # InvalidArityError + + # Raised when a block is passed to a memoized method + class BlockNotAllowedError < ArgumentError + + # Initialize a block not allowed exception + # + # @param [Module] descendant + # @param [Symbol] method + # + # @api private + def initialize(descendant, method) + super("Cannot pass a block to #{descendant}##{method}, it is memoized") + end + + end # BlockNotAllowedError + + # The original method before memoization + # + # @return [UnboundMethod] + # + # @api public + attr_reader :original_method + + # Initialize an object to build a memoized method + # + # @param [Module] descendant + # @param [Symbol] method_name + # @param [#call] freezer + # + # @return [undefined] + # + # @api private + def initialize(descendant, method_name, freezer) + @descendant = descendant + @method_name = method_name + @freezer = freezer + @original_visibility = visibility + @original_method = @descendant.instance_method(@method_name) + assert_arity(@original_method.arity) + end + + # Build a new memoized method + # + # @example + # method_builder.call # => creates new method + # + # @return [MethodBuilder] + # + # @api public + def call + remove_original_method + create_memoized_method + set_method_visibility + self + end + + private + + # Assert the method arity is zero + # + # @param [Integer] arity + # + # @return [undefined] + # + # @raise [InvalidArityError] + # + # @api private + def assert_arity(arity) + if arity.nonzero? + fail InvalidArityError.new(@descendant, @method_name, arity) + end + end + + # Remove the original method + # + # @return [undefined] + # + # @api private + def remove_original_method + name = @method_name + @descendant.module_eval { undef_method(name) } + end + + # Create a new memoized method + # + # @return [undefined] + # + # @api private + def create_memoized_method + name, method, freezer = @method_name, @original_method, @freezer + @descendant.module_eval do + define_method(name) do |&block| + fail BlockNotAllowedError.new(self.class, name) if block + memoized_method_cache.fetch(name) do + freezer.call(method.bind(self).call) + end + end + end + end + + # Set the memoized method visibility to match the original method + # + # @return [undefined] + # + # @api private + def set_method_visibility + @descendant.send(@original_visibility, @method_name) + end + + # Get the visibility of the original method + # + # @return [Symbol] + # + # @api private + def visibility + if @descendant.private_method_defined?(@method_name) then :private + elsif @descendant.protected_method_defined?(@method_name) then :protected + else :public + end + end + + end # MethodBuilder +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable/module_methods.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable/module_methods.rb new file mode 100644 index 0000000..1ffdf34 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable/module_methods.rb @@ -0,0 +1,125 @@ +# encoding: utf-8 + +module Memoizable + + # Methods mixed in to memoizable singleton classes + module ModuleMethods + + # Return default deep freezer + # + # @return [#call] + # + # @api private + def freezer + Freezer + end + + # Memoize a list of methods + # + # @example + # memoize :hash + # + # @param [Array] methods + # a list of methods to memoize + # + # @return [self] + # + # @api public + def memoize(*methods) + methods.each(&method(:memoize_method)) + self + end + + # Test if an instance method is memoized + # + # @example + # class Foo + # include Memoizable + # + # def bar + # end + # memoize :bar + # end + # + # Foo.memoized?(:bar) # true + # Foo.memoized?(:baz) # false + # + # @param [Symbol] name + # + # @return [Boolean] + # true if method is memoized, false if not + # + # @api private + def memoized?(name) + memoized_methods.key?(name) + end + + # Return unmemoized instance method + # + # @example + # + # class Foo + # include Memoizable + # + # def bar + # end + # memoize :bar + # end + # + # Foo.unmemoized_instance_method(:bar) + # + # @param [Symbol] name + # + # @return [UnboundMethod] + # the memoized method + # + # @raise [NameError] + # raised if the method is unknown + # + # @api public + def unmemoized_instance_method(name) + memoized_methods[name].original_method + end + + private + + # Hook called when module is included + # + # @param [Module] descendant + # the module including ModuleMethods + # + # @return [self] + # + # @api private + def included(descendant) + super + descendant.module_eval { include Memoizable } + end + + # Memoize the named method + # + # @param [Symbol] method_name + # a method name to memoize + # + # @return [undefined] + # + # @api private + def memoize_method(method_name) + memoized_methods[method_name] = MethodBuilder.new( + self, + method_name, + freezer + ).call + end + + # Return method builder registry + # + # @return [Hash] + # + # @api private + def memoized_methods + @_memoized_methods ||= Memory.new + end + + end # ModuleMethods +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/lib/memoizable/version.rb b/.gems/gems/memoizable-0.4.2/lib/memoizable/version.rb new file mode 100644 index 0000000..40c0e89 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/lib/memoizable/version.rb @@ -0,0 +1,8 @@ +# encoding: utf-8 + +module Memoizable + + # Gem version + VERSION = '0.4.2'.freeze + +end # Memoizable diff --git a/.gems/gems/memoizable-0.4.2/memoizable.gemspec b/.gems/gems/memoizable-0.4.2/memoizable.gemspec new file mode 100644 index 0000000..533e841 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/memoizable.gemspec @@ -0,0 +1,24 @@ +# encoding: utf-8 + +require File.expand_path('../lib/memoizable/version', __FILE__) + +Gem::Specification.new do |gem| + gem.name = 'memoizable' + gem.version = Memoizable::VERSION.dup + gem.authors = ['Dan Kubb', 'Erik Michaels-Ober'] + gem.email = ['dan.kubb@gmail.com', 'sferik@gmail.com'] + gem.description = 'Memoize method return values' + gem.summary = gem.description + gem.homepage = 'https://github.com/dkubb/memoizable' + gem.license = 'MIT' + + gem.require_paths = %w[lib] + gem.files = %w[CONTRIBUTING.md LICENSE.md README.md Rakefile memoizable.gemspec] + gem.files += Dir.glob('{lib,spec}/**/*.rb') + gem.test_files = Dir.glob('spec/{unit,integration}/**/*.rb') + gem.extra_rdoc_files = Dir.glob('**/*.md') + + gem.add_runtime_dependency('thread_safe', '~> 0.3', '>= 0.3.1') + + gem.add_development_dependency('bundler', '~> 1.5', '>= 1.5.3') +end diff --git a/.gems/gems/memoizable-0.4.2/spec/integration/serializable_spec.rb b/.gems/gems/memoizable-0.4.2/spec/integration/serializable_spec.rb new file mode 100644 index 0000000..acd194f --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/integration/serializable_spec.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 + +require 'spec_helper' + +class Serializable + include Memoizable + + def random_number + rand(10000) + end + memoize :random_number +end + +describe 'A serializable object' do + let(:serializable) do + Serializable.new + end + + before do + serializable.random_number # Call the memoized method to trigger lazy memoization + end + + it 'is serializable with Marshal' do + expect { Marshal.dump(serializable) }.not_to raise_error + end + + it 'is deserializable with Marshal' do + serialized = Marshal.dump(serializable) + deserialized = Marshal.load(serialized) + + expect(deserialized).to be_an_instance_of(Serializable) + expect(deserialized.random_number).to eql(serializable.random_number) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/shared/call_super_shared_spec.rb b/.gems/gems/memoizable-0.4.2/spec/shared/call_super_shared_spec.rb new file mode 100644 index 0000000..9368180 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/shared/call_super_shared_spec.rb @@ -0,0 +1,23 @@ +# encoding: utf-8 + +shared_examples 'it calls super' do |method| + around do |example| + # Restore original method after each example + original = "original_#{method}" + superclass.class_eval do + alias_method original, method + example.call + undef_method method + alias_method method, original + end + end + + it "delegates to the superclass ##{method} method" do + # This is the most succinct approach I could think of to test whether the + # superclass method is called. All of the built-in rspec helpers did not + # seem to work for this. + called = false + superclass.class_eval { define_method(method) { |_| called = true } } + expect { subject }.to change { called }.from(false).to(true) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/shared/command_method_behavior.rb b/.gems/gems/memoizable-0.4.2/spec/shared/command_method_behavior.rb new file mode 100644 index 0000000..c62854d --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/shared/command_method_behavior.rb @@ -0,0 +1,7 @@ +# encoding: utf-8 + +shared_examples_for 'a command method' do + it 'returns self' do + should equal(object) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/spec_helper.rb b/.gems/gems/memoizable-0.4.2/spec/spec_helper.rb new file mode 100644 index 0000000..1bedf5f --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/spec_helper.rb @@ -0,0 +1,33 @@ +# encoding: utf-8 + +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] + +SimpleCov.start do + command_name 'spec' + + add_filter 'config' + add_filter 'spec' + add_filter 'vendor' + + minimum_coverage 100 +end + +require 'memoizable' +require 'rspec' + +# Require spec support files and shared behavior +Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each do |file| + require file.chomp('.rb') +end + +RSpec.configure do |config| + config.expect_with :rspec do |expect_with| + expect_with.syntax = :expect + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/class_methods/included_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/class_methods/included_spec.rb new file mode 100644 index 0000000..4460f52 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/class_methods/included_spec.rb @@ -0,0 +1,18 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Memoizable, '.included' do + subject { object.class_eval { include Memoizable } } + + let(:object) { Class.new } + let(:superclass) { Module } + + it_behaves_like 'it calls super', :included + + it 'extends the descendant with module methods' do + subject + extended_modules = class << object; included_modules end + expect(extended_modules).to include(Memoizable::ModuleMethods) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/fixtures/classes.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/fixtures/classes.rb new file mode 100644 index 0000000..9b35180 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/fixtures/classes.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 + +module Fixture + class Object + include Memoizable + + def required_arguments(foo) + end + + def optional_arguments(foo = nil) + end + + def test + 'test' + end + + def zero_arity + caller + end + + def one_arity(arg) + end + + def public_method + caller + end + + protected + + def protected_method + caller + end + + private + + def private_method + caller + end + + end # class Object +end # module Fixture diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/freeze_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/freeze_spec.rb new file mode 100644 index 0000000..6b776e9 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/freeze_spec.rb @@ -0,0 +1,27 @@ +# encoding: utf-8 + +require 'spec_helper' +require File.expand_path('../../fixtures/classes', __FILE__) + +describe Memoizable::InstanceMethods, '#freeze' do + subject { object.freeze } + + let(:described_class) { Class.new(Fixture::Object) } + + before do + described_class.memoize(:test) + end + + let(:object) { described_class.allocate } + + it_should_behave_like 'a command method' + + it 'freezes the object' do + expect { subject }.to change(object, :frozen?).from(false).to(true) + end + + it 'allows methods not yet called to be memoized' do + subject + expect(object.test).to be(object.test) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/memoize_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/memoize_spec.rb new file mode 100644 index 0000000..e75f4b3 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/instance_methods/memoize_spec.rb @@ -0,0 +1,40 @@ +# encoding: utf-8 + +require 'spec_helper' +require File.expand_path('../../fixtures/classes', __FILE__) + +describe Memoizable::InstanceMethods, '#memoize' do + subject { object.memoize(method => value) } + + let(:described_class) { Class.new(Fixture::Object) } + let(:object) { described_class.new } + let(:method) { :test } + + before do + described_class.memoize(method) + end + + context 'when the method is not memoized' do + let(:value) { String.new } + + it 'sets the memoized value for the method to the value' do + subject + expect(object.send(method)).to be(value) + end + + it_should_behave_like 'a command method' + end + + context 'when the method is already memoized' do + let(:value) { double } + let(:original) { nil } + + before do + object.memoize(method => original) + end + + it 'raises an exception' do + expect { subject }.to raise_error(ArgumentError) + end + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/memory_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/memory_spec.rb new file mode 100644 index 0000000..faf5a54 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/memory_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Memoizable::Memory do + let(:memory) { Memoizable::Memory.new } + + context "serialization" do + let(:deserialized) { Marshal.load(Marshal.dump(memory)) } + + it 'is serializable with Marshal' do + expect { Marshal.dump(memory) }.not_to raise_error + end + + it 'is deserializable with Marshal' do + expect(deserialized).to be_an_instance_of(Memoizable::Memory) + end + + it 'mantains the same class of cache when deserialized' do + original_cache = memory.instance_variable_get(:@memory) + deserialized_cache = deserialized.instance_variable_get(:@memory) + + expect(deserialized_cache.class).to eql(original_cache.class) + end + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/call_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/call_spec.rb new file mode 100644 index 0000000..f81c1c7 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/call_spec.rb @@ -0,0 +1,93 @@ +# encoding: utf-8 + +require 'spec_helper' +require File.expand_path('../../fixtures/classes', __FILE__) + +describe Memoizable::MethodBuilder, '#call' do + subject { object.call } + + let(:object) { described_class.new(descendant, method_name, freezer) } + let(:freezer) { lambda { |object| object.freeze } } + let(:instance) { descendant.new } + + let(:descendant) do + Class.new do + include Memoizable + + def public_method + __method__.to_s + end + + def protected_method + __method__.to_s + end + protected :protected_method + + def private_method + __method__.to_s + end + private :private_method + end + end + + shared_examples_for 'Memoizable::MethodBuilder#call' do + it_should_behave_like 'a command method' + + it 'creates a method that is memoized' do + subject + expect(instance.send(method_name)).to be(instance.send(method_name)) + end + + it 'creates a method that returns the expected value' do + subject + expect(instance.send(method_name)).to eql(method_name.to_s) + end + + it 'creates a method that returns a frozen value' do + subject + expect(descendant.new.send(method_name)).to be_frozen + end + + it 'creates a method that does not accept a block' do + subject + expect { descendant.new.send(method_name) {} }.to raise_error( + described_class::BlockNotAllowedError, + "Cannot pass a block to #{descendant}##{method_name}, it is memoized" + ) + end + end + + context 'public method' do + let(:method_name) { :public_method } + + it_should_behave_like 'Memoizable::MethodBuilder#call' + + it 'creates a public memoized method' do + subject + expect(descendant).to be_public_method_defined(method_name) + end + end + + context 'protected method' do + let(:method_name) { :protected_method } + + it_should_behave_like 'Memoizable::MethodBuilder#call' + + it 'creates a protected memoized method' do + subject + expect(descendant).to be_protected_method_defined(method_name) + end + + end + + context 'private method' do + let(:method_name) { :private_method } + + it_should_behave_like 'Memoizable::MethodBuilder#call' + + it 'creates a private memoized method' do + subject + expect(descendant).to be_private_method_defined(method_name) + end + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/class_methods/new_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/class_methods/new_spec.rb new file mode 100644 index 0000000..7bb247c --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/class_methods/new_spec.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 + +require 'spec_helper' +require File.expand_path('../../../fixtures/classes', __FILE__) + +describe Memoizable::MethodBuilder, '.new' do + subject { described_class.new(descendant, method_name, freezer) } + + let(:descendant) { Fixture::Object } + let(:freezer) { lambda { |object| object.freeze } } + + context 'with a zero arity method' do + let(:method_name) { :zero_arity } + + it { should be_instance_of(described_class) } + + it 'sets the original method' do + # original method is not memoized + method = subject.original_method.bind(descendant.new) + expect(method.call).to_not be(method.call) + end + end + + context 'with a one arity method' do + let(:method_name) { :one_arity } + + it 'raises an exception' do + expect { subject }.to raise_error( + described_class::InvalidArityError, + 'Cannot memoize Fixture::Object#one_arity, its arity is 1' + ) + end + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/original_method_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/original_method_spec.rb new file mode 100644 index 0000000..8d37b3f --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/method_builder/original_method_spec.rb @@ -0,0 +1,31 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Memoizable::MethodBuilder, '#original_method' do + subject { object.original_method } + + let(:object) { described_class.new(descendant, method_name, freezer) } + let(:method_name) { :foo } + let(:freezer) { lambda { |object| object.freeze } } + + let(:descendant) do + Class.new do + def initialize + @foo = 0 + end + + def foo + @foo += 1 + end + end + end + + it { should be_instance_of(UnboundMethod) } + + it 'returns the original method' do + # original method is not memoized + method = subject.bind(descendant.new) + expect(method.call).to_not be(method.call) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/included_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/included_spec.rb new file mode 100644 index 0000000..4ac2139 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/included_spec.rb @@ -0,0 +1,23 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Memoizable::ModuleMethods, '#included' do + subject { descendant.instance_exec(object) { |mod| include mod } } + + let(:object) { Module.new.extend(described_class) } + let(:descendant) { Class.new } + let(:superclass) { Module } + + before do + # Prevent Module.included from being called through inheritance + allow(Memoizable).to receive(:included) + end + + it_behaves_like 'it calls super', :included + + it 'includes Memoizable into the descendant' do + subject + expect(descendant.included_modules).to include(Memoizable) + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoize_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoize_spec.rb new file mode 100644 index 0000000..917f68b --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoize_spec.rb @@ -0,0 +1,123 @@ +# encoding: utf-8 + +require 'spec_helper' +require File.expand_path('../../fixtures/classes', __FILE__) + +shared_examples_for 'memoizes method' do + it 'memoizes the instance method' do + subject + instance = object.new + expect(instance.send(method)).to be(instance.send(method)) + end + + it 'creates a zero arity method', :unless => RUBY_VERSION == '1.8.7' do + subject + expect(object.new.method(method).arity).to be_zero + end + + context 'when the initializer calls the memoized method' do + before do + method = self.method + object.send(:define_method, :initialize) { send(method) } + end + + it 'allows the memoized method to be called within the initializer' do + subject + expect { object.new }.to_not raise_error + end + end +end + +describe Memoizable::ModuleMethods, '#memoize' do + subject { object.memoize(method) } + + let(:object) do + stub_const 'TestClass', Class.new(Fixture::Object) { + def some_state + Object.new + end + } + end + + context 'on method with required arguments' do + let(:method) { :required_arguments } + + it 'should raise error' do + expect { subject }.to raise_error( + Memoizable::MethodBuilder::InvalidArityError, + 'Cannot memoize TestClass#required_arguments, its arity is 1' + ) + end + end + + context 'on method with optional arguments' do + let(:method) { :optional_arguments } + + it 'should raise error' do + expect { subject }.to raise_error( + Memoizable::MethodBuilder::InvalidArityError, + 'Cannot memoize TestClass#optional_arguments, its arity is -1' + ) + end + end + + context 'memoized method that returns generated values' do + let(:method) { :some_state } + + it_should_behave_like 'a command method' + it_should_behave_like 'memoizes method' + + it 'creates a method that returns a frozen value' do + subject + expect(object.new.send(method)).to be_frozen + end + end + + context 'public method' do + let(:method) { :public_method } + + it_should_behave_like 'a command method' + it_should_behave_like 'memoizes method' + + it 'is still a public method' do + should be_public_method_defined(method) + end + + it 'creates a method that returns a frozen value' do + subject + expect(object.new.send(method)).to be_frozen + end + end + + context 'protected method' do + let(:method) { :protected_method } + + it_should_behave_like 'a command method' + it_should_behave_like 'memoizes method' + + it 'is still a protected method' do + should be_protected_method_defined(method) + end + + it 'creates a method that returns a frozen value' do + subject + expect(object.new.send(method)).to be_frozen + end + end + + context 'private method' do + let(:method) { :private_method } + + it_should_behave_like 'a command method' + it_should_behave_like 'memoizes method' + + it 'is still a private method' do + should be_private_method_defined(method) + end + + it 'creates a method that returns a frozen value' do + subject + expect(object.new.send(method)).to be_frozen + end + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoized_predicate_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoized_predicate_spec.rb new file mode 100644 index 0000000..c54674d --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/memoized_predicate_spec.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Memoizable::ModuleMethods, '#memoized?' do + let(:object) do + Class.new do + include Memoizable + def foo + end + memoize :foo + end + end + + subject { object.memoized?(name) } + + context 'with memoized method' do + let(:name) { :foo } + + it { should be(true) } + end + + context 'with non memoized method' do + let(:name) { :bar } + + it { should be(false) } + end +end diff --git a/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/unmemoized_instance_method_spec.rb b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/unmemoized_instance_method_spec.rb new file mode 100644 index 0000000..0f6fb98 --- /dev/null +++ b/.gems/gems/memoizable-0.4.2/spec/unit/memoizable/module_methods/unmemoized_instance_method_spec.rb @@ -0,0 +1,43 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Memoizable::ModuleMethods, '#unmemoized_instance_method' do + subject { object.unmemoized_instance_method(name) } + + let(:object) do + Class.new do + include Memoizable + + def initialize + @foo = 0 + end + + def foo + @foo += 1 + end + + memoize :foo + end + end + + context 'when the method was memoized' do + let(:name) { :foo } + + it { should be_instance_of(UnboundMethod) } + + it 'returns the original method' do + # original method is not memoized + method = subject.bind(object.new) + expect(method.call).to_not be(method.call) + end + end + + context 'when the method was not memoized' do + let(:name) { :bar } + + it 'raises an exception' do + expect { subject }.to raise_error(NameError, 'No method bar is memoized') + end + end +end diff --git a/.gems/gems/multipart-post-2.0.0/.gitignore b/.gems/gems/multipart-post-2.0.0/.gitignore new file mode 100644 index 0000000..c02a141 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/.gitignore @@ -0,0 +1,6 @@ +doc +pkg +*~ +*.swo +*.swp +/Gemfile.lock diff --git a/.gems/gems/multipart-post-2.0.0/.travis.yml b/.gems/gems/multipart-post-2.0.0/.travis.yml new file mode 100644 index 0000000..e1ea1fe --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/.travis.yml @@ -0,0 +1,7 @@ +rvm: + - 1.9.3 + - 2.0.0 + - jruby +branches: + only: + - master diff --git a/.gems/gems/multipart-post-2.0.0/Gemfile b/.gems/gems/multipart-post-2.0.0/Gemfile new file mode 100644 index 0000000..e55151b --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org' +gemspec + +platforms :mri_19 do + gem 'ruby-debug19' +end + +platforms :mri_18 do + gem 'ruby-debug' +end + +group :development, :test do + gem 'rake' +end diff --git a/.gems/gems/multipart-post-2.0.0/History.txt b/.gems/gems/multipart-post-2.0.0/History.txt new file mode 100644 index 0000000..64b1e38 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/History.txt @@ -0,0 +1,60 @@ +=== 2.0.0 / 2013-12-21 + +- Drop Ruby 1.8 compatibility +- GH #21: Fix FilePart length calculation for Ruby 1.9 when filename contains + multibyte characters (hexfet) +- GH #20: Ensure upload responds to both #content_type and #original_filename + (Steven Davidovitz) +- GH #31: Support setting headers on any part of the request (Socrates Vicente) +- GH #30: Support array values for params (Gustav Ernberg) +- GH #32: Fix respond_to? signature (Leo Cassarani) +- GH #33: Update README to markdown (Jagtesh Chadha) +- GH #35: Improved handling of array-type parameters (Steffen Grunwald) + +=== 1.2.0 / 2013-02-25 + +- #25: Ruby 2 compatibility (thanks mislav) + +=== 1.1.5 / 2012-02-12 + +- Fix length/bytesize of parts in 1.9 (#7, #14) (Jason Moore) +- Allow CompositeIO objects to be re-read by rewinding, like other IO + objects. (Luke Redpath) + +=== 1.1.4 / 2011-11-23 + +- Non-functional changes in release (switch to Bundler gem tasks) + +=== 1.1.3 / 2011-07-25 + +- More configurable header specification for parts (Gerrit Riessen) + +=== 1.1.2 / 2011-05-24 + +- Fix CRLF file part miscalculation (Johannes Wagener) +- Fix Epilogue CRLF issue (suggestion by Neil Spring) + +=== 1.1.1 / 2011-05-13 + +- GH# 9: Fixed Ruby 1.9.2 StringIO bug (thanks Alex Koppel) + +=== 1.1.0 / 2011-01-11 + +- API CHANGE: UploadIO.convert! removed in favor of UploadIO.new + (Jeff Hodges) + +=== 1.0.1 / 2010-04-27 + +- Doc updates, make gemspec based on more modern Rubygems + +=== 1.0 / 2009-02-12 + +- Many fixes from mlooney, seems to work now. Putting the 0.9 seal of + approval on it. + +=== 0.1 / 2008-08-12 + +* 1 major enhancement + + * Birthday! + diff --git a/.gems/gems/multipart-post-2.0.0/Manifest.txt b/.gems/gems/multipart-post-2.0.0/Manifest.txt new file mode 100644 index 0000000..109d8f0 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/Manifest.txt @@ -0,0 +1,9 @@ +lib/composite_io.rb +lib/multipartable.rb +lib/parts.rb +lib/net/http/post/multipart.rb +Manifest.txt +Rakefile +README.txt +test/test_composite_io.rb +test/net/http/post/test_multipart.rb diff --git a/.gems/gems/multipart-post-2.0.0/README.md b/.gems/gems/multipart-post-2.0.0/README.md new file mode 100644 index 0000000..fc7eae1 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/README.md @@ -0,0 +1,77 @@ +## multipart-post + +* http://github.com/nicksieger/multipart-post + +![build status](https://travis-ci.org/nicksieger/multipart-post.png) + +#### DESCRIPTION: + +Adds a streamy multipart form post capability to Net::HTTP. Also +supports other methods besides POST. + +#### FEATURES/PROBLEMS: + +* Appears to actually work. A good feature to have. +* Encapsulates posting of file/binary parts and name/value parameter parts, similar to + most browsers' file upload forms. +* Provides an UploadIO helper class to prepare IO objects for inclusion in the params + hash of the multipart post object. + +#### SYNOPSIS: + + require 'net/http/post/multipart' + + url = URI.parse('http://www.example.com/upload') + File.open("./image.jpg") do |jpg| + req = Net::HTTP::Post::Multipart.new url.path, + "file" => UploadIO.new(jpg, "image/jpeg", "image.jpg") + res = Net::HTTP.start(url.host, url.port) do |http| + http.request(req) + end + end + +To post multiple files or attachments, simply include multiple parameters with +UploadIO values: + + require 'net/http/post/multipart' + + url = URI.parse('http://www.example.com/upload') + req = Net::HTTP::Post::Multipart.new url.path, + "file1" => UploadIO.new(File.new("./image.jpg"), "image/jpeg", "image.jpg"), + "file2" => UploadIO.new(File.new("./image2.jpg"), "image/jpeg", "image2.jpg") + res = Net::HTTP.start(url.host, url.port) do |http| + http.request(req) + end + +#### REQUIREMENTS: + +None + +#### INSTALL: + + gem install multipart-post + +#### LICENSE: + +(The MIT License) + +Copyright (c) 2007-2013 Nick Sieger + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/multipart-post-2.0.0/Rakefile b/.gems/gems/multipart-post-2.0.0/Rakefile new file mode 100644 index 0000000..dd85431 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/Rakefile @@ -0,0 +1,9 @@ +require "bundler/gem_tasks" + +task :default => :test + +require 'rake/testtask' +Rake::TestTask.new do |t| + t.libs << "test" + t.test_files = FileList['test/**/test*.rb'] +end diff --git a/.gems/gems/multipart-post-2.0.0/lib/composite_io.rb b/.gems/gems/multipart-post-2.0.0/lib/composite_io.rb new file mode 100644 index 0000000..4ba7cf5 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/lib/composite_io.rb @@ -0,0 +1,108 @@ +#-- +# Copyright (c) 2007-2012 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +# Concatenate together multiple IO objects into a single, composite IO object +# for purposes of reading as a single stream. +# +# Usage: +# +# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three')) +# puts crio.read # => "onetwothree" +# +class CompositeReadIO + # Create a new composite-read IO from the arguments, all of which should + # respond to #read in a manner consistent with IO. + def initialize(*ios) + @ios = ios.flatten + @index = 0 + end + + # Read from IOs in order until `length` bytes have been received. + def read(length = nil, outbuf = nil) + got_result = false + outbuf = outbuf ? outbuf.replace("") : "" + + while io = current_io + if result = io.read(length) + got_result ||= !result.nil? + result.force_encoding("BINARY") if result.respond_to?(:force_encoding) + outbuf << result + length -= result.length if length + break if length == 0 + end + advance_io + end + (!got_result && length) ? nil : outbuf + end + + def rewind + @ios.each { |io| io.rewind } + @index = 0 + end + + private + + def current_io + @ios[@index] + end + + def advance_io + @index += 1 + end +end + +# Convenience methods for dealing with files and IO that are to be uploaded. +class UploadIO + # Create an upload IO suitable for including in the params hash of a + # Net::HTTP::Post::Multipart. + # + # Can take two forms. The first accepts a filename and content type, and + # opens the file for reading (to be closed by finalizer). + # + # The second accepts an already-open IO, but also requires a third argument, + # the filename from which it was opened (particularly useful/recommended if + # uploading directly from a form in a framework, which often save the file to + # an arbitrarily named RackMultipart file in /tmp). + # + # Usage: + # + # UploadIO.new("file.txt", "text/plain") + # UploadIO.new(file_io, "text/plain", "file.txt") + # + attr_reader :content_type, :original_filename, :local_path, :io, :opts + + def initialize(filename_or_io, content_type, filename = nil, opts = {}) + io = filename_or_io + local_path = "" + if io.respond_to? :read + # in Ruby 1.9.2, StringIOs no longer respond to path + # (since they respond to :length, so we don't need their local path, see parts.rb:41) + local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path" + else + io = File.open(filename_or_io) + local_path = filename_or_io + end + filename ||= local_path + + @content_type = content_type + @original_filename = File.basename(filename) + @local_path = local_path + @io = io + @opts = opts + end + + def self.convert!(io, content_type, original_filename, local_path) + raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code." + end + + def method_missing(*args) + @io.send(*args) + end + + def respond_to?(meth, include_all = false) + @io.respond_to?(meth, include_all) || super(meth, include_all) + end +end diff --git a/.gems/gems/multipart-post-2.0.0/lib/multipart_post.rb b/.gems/gems/multipart-post-2.0.0/lib/multipart_post.rb new file mode 100644 index 0000000..76540a8 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/lib/multipart_post.rb @@ -0,0 +1,9 @@ +#-- +# Copyright (c) 2007-2013 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +module MultipartPost + VERSION = "2.0.0" +end diff --git a/.gems/gems/multipart-post-2.0.0/lib/multipartable.rb b/.gems/gems/multipart-post-2.0.0/lib/multipartable.rb new file mode 100644 index 0000000..28fa41e --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/lib/multipartable.rb @@ -0,0 +1,29 @@ +#-- +# Copyright (c) 2007-2013 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +require 'parts' + module Multipartable + DEFAULT_BOUNDARY = "-----------RubyMultipartPost" + def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY) + headers = headers.clone # don't want to modify the original variable + parts_headers = headers.delete(:parts) || {} + super(path, headers) + parts = params.map do |k,v| + case v + when Array + v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) } + else + Parts::Part.new(boundary, k, v, parts_headers[k]) + end + end.flatten + parts << Parts::EpiloguePart.new(boundary) + ios = parts.map {|p| p.to_io } + self.set_content_type(headers["Content-Type"] || "multipart/form-data", + { "boundary" => boundary }) + self.content_length = parts.inject(0) {|sum,i| sum + i.length } + self.body_stream = CompositeReadIO.new(*ios) + end + end diff --git a/.gems/gems/multipart-post-2.0.0/lib/net/http/post/multipart.rb b/.gems/gems/multipart-post-2.0.0/lib/net/http/post/multipart.rb new file mode 100644 index 0000000..7570582 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/lib/net/http/post/multipart.rb @@ -0,0 +1,27 @@ +#-- +# Copyright (c) 2007-2012 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +require 'net/http' +require 'stringio' +require 'cgi' +require 'composite_io' +require 'multipartable' +require 'parts' + +module Net #:nodoc: + class HTTP #:nodoc: + class Put + class Multipart < Put + include Multipartable + end + end + class Post #:nodoc: + class Multipart < Post + include Multipartable + end + end + end +end diff --git a/.gems/gems/multipart-post-2.0.0/lib/parts.rb b/.gems/gems/multipart-post-2.0.0/lib/parts.rb new file mode 100644 index 0000000..c06cbd9 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/lib/parts.rb @@ -0,0 +1,96 @@ +#-- +# Copyright (c) 2007-2013 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +module Parts + module Part #:nodoc: + def self.new(boundary, name, value, headers = {}) + headers ||= {} # avoid nil values + if file?(value) + FilePart.new(boundary, name, value, headers) + else + ParamPart.new(boundary, name, value, headers) + end + end + + def self.file?(value) + value.respond_to?(:content_type) && value.respond_to?(:original_filename) + end + + def length + @part.length + end + + def to_io + @io + end + end + + class ParamPart + include Part + def initialize(boundary, name, value, headers = {}) + @part = build_part(boundary, name, value, headers) + @io = StringIO.new(@part) + end + + def length + @part.bytesize + end + + def build_part(boundary, name, value, headers = {}) + part = '' + part << "--#{boundary}\r\n" + part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n" + part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"] + part << "\r\n" + part << "#{value}\r\n" + end + end + + # Represents a part to be filled from file IO. + class FilePart + include Part + attr_reader :length + def initialize(boundary, name, io, headers = {}) + file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path) + @head = build_head(boundary, name, io.original_filename, io.content_type, file_length, + io.respond_to?(:opts) ? io.opts.merge(headers) : headers) + @foot = "\r\n" + @length = @head.bytesize + file_length + @foot.length + @io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot)) + end + + def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {}) + trans_encoding = opts["Content-Transfer-Encoding"] || "binary" + content_disposition = opts["Content-Disposition"] || "form-data" + + part = '' + part << "--#{boundary}\r\n" + part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n" + part << "Content-Length: #{content_len}\r\n" + if content_id = opts["Content-ID"] + part << "Content-ID: #{content_id}\r\n" + end + + if headers["Content-Type"] != nil + part << "Content-Type: " + headers["Content-Type"] + "\r\n" + else + part << "Content-Type: #{type}\r\n" + end + + part << "Content-Transfer-Encoding: #{trans_encoding}\r\n" + part << "\r\n" + end + end + + # Represents the epilogue or closing boundary. + class EpiloguePart + include Part + def initialize(boundary) + @part = "--#{boundary}--\r\n\r\n" + @io = StringIO.new(@part) + end + end +end diff --git a/.gems/gems/multipart-post-2.0.0/multipart-post.gemspec b/.gems/gems/multipart-post-2.0.0/multipart-post.gemspec new file mode 100644 index 0000000..6954f09 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/multipart-post.gemspec @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "multipart_post" + +Gem::Specification.new do |s| + s.name = "multipart-post" + s.version = MultipartPost::VERSION + s.authors = ["Nick Sieger"] + s.email = ["nick@nicksieger.com"] + s.homepage = "https://github.com/nicksieger/multipart-post" + s.summary = %q{A multipart form post accessory for Net::HTTP.} + s.license = "MIT" + s.description = %q{Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file.} + + s.rubyforge_project = "caldersphere" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.rdoc_options = ["--main", "README.md", "-SHN", "-f", "darkfish"] + s.require_paths = ["lib"] +end diff --git a/.gems/gems/multipart-post-2.0.0/test/multibyte.txt b/.gems/gems/multipart-post-2.0.0/test/multibyte.txt new file mode 100644 index 0000000..24a84b0 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/test/multibyte.txt @@ -0,0 +1 @@ +ファイル diff --git a/.gems/gems/multipart-post-2.0.0/test/net/http/post/test_multipart.rb b/.gems/gems/multipart-post-2.0.0/test/net/http/post/test_multipart.rb new file mode 100644 index 0000000..c127e0a --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/test/net/http/post/test_multipart.rb @@ -0,0 +1,110 @@ +#-- +# Copyright (c) 2007-2013 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +require 'net/http/post/multipart' +require 'test/unit' + +class Net::HTTP::Post::MultiPartTest < Test::Unit::TestCase + TEMP_FILE = "temp.txt" + + HTTPPost = Struct.new("HTTPPost", :content_length, :body_stream, :content_type) + HTTPPost.module_eval do + def set_content_type(type, params = {}) + self.content_type = type + params.map{|k,v|"; #{k}=#{v}"}.join('') + end + end + + def teardown + File.delete(TEMP_FILE) rescue nil + end + + def test_form_multipart_body + File.open(TEMP_FILE, "w") {|f| f << "1234567890"} + @io = File.open(TEMP_FILE) + @io = UploadIO.new @io, "text/plain", TEMP_FILE + assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io) + end + def test_form_multipart_body_put + File.open(TEMP_FILE, "w") {|f| f << "1234567890"} + @io = File.open(TEMP_FILE) + @io = UploadIO.new @io, "text/plain", TEMP_FILE + assert_results Net::HTTP::Put::Multipart.new("/foo/bar", :foo => 'bar', :file => @io) + end + + def test_form_multipart_body_with_stringio + @io = StringIO.new("1234567890") + @io = UploadIO.new @io, "text/plain", TEMP_FILE + assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io) + end + + def test_form_multiparty_body_with_parts_headers + @io = StringIO.new("1234567890") + @io = UploadIO.new @io, "text/plain", TEMP_FILE + parts = { :text => 'bar', :file => @io } + headers = { + :parts => { + :text => { "Content-Type" => "part/type" }, + :file => { "Content-Transfer-Encoding" => "part-encoding" } + } + } + + request = Net::HTTP::Post::Multipart.new("/foo/bar", parts, headers) + assert_results request + assert_additional_headers_added(request, headers[:parts]) + end + + def test_form_multipart_body_with_array_value + File.open(TEMP_FILE, "w") {|f| f << "1234567890"} + @io = File.open(TEMP_FILE) + @io = UploadIO.new @io, "text/plain", TEMP_FILE + params = {:foo => ['bar', 'quux'], :file => @io} + headers = { :parts => { + :foo => { "Content-Type" => "application/json; charset=UTF-8" } } } + post = Net::HTTP::Post::Multipart.new("/foo/bar", params, headers, + Net::HTTP::Post::Multipart::DEFAULT_BOUNDARY) + + assert post.content_length && post.content_length > 0 + assert post.body_stream + + body = post.body_stream.read + assert_equal 2, body.lines.grep(/name="foo"/).length + assert body =~ /Content-Type: application\/json; charset=UTF-8/, body + end + + def test_form_multipart_body_with_arrayparam + File.open(TEMP_FILE, "w") {|f| f << "1234567890"} + @io = File.open(TEMP_FILE) + @io = UploadIO.new @io, "text/plain", TEMP_FILE + assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :multivalueParam => ['bar','bah'], :file => @io) + end + + def assert_results(post) + assert post.content_length && post.content_length > 0 + assert post.body_stream + assert_equal "multipart/form-data; boundary=#{Multipartable::DEFAULT_BOUNDARY}", post['content-type'] + body = post.body_stream.read + boundary_regex = Regexp.quote Multipartable::DEFAULT_BOUNDARY + assert body =~ /1234567890/ + # ensure there is at least one boundary + assert body =~ /^--#{boundary_regex}\r\n/ + # ensure there is an epilogue + assert body =~ /^--#{boundary_regex}--\r\n/ + assert body =~ /text\/plain/ + if (body =~ /multivalueParam/) + assert_equal 2, body.scan(/^.*multivalueParam.*$/).size + end + end + + def assert_additional_headers_added(post, parts_headers) + post.body_stream.rewind + body = post.body_stream.read + parts_headers.each do |part, headers| + headers.each do |k,v| + assert body =~ /#{k}: #{v}/ + end + end + end +end diff --git a/.gems/gems/multipart-post-2.0.0/test/test_composite_io.rb b/.gems/gems/multipart-post-2.0.0/test/test_composite_io.rb new file mode 100644 index 0000000..6e8a193 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/test/test_composite_io.rb @@ -0,0 +1,115 @@ +#-- +# Copyright (c) 2007-2013 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +require 'composite_io' +require 'stringio' +require 'test/unit' +require 'timeout' + +class CompositeReadIOTest < Test::Unit::TestCase + def setup + @io = CompositeReadIO.new(CompositeReadIO.new(StringIO.new('the '), StringIO.new('quick ')), + StringIO.new('brown '), StringIO.new('fox')) + end + + def test_full_read_from_several_ios + assert_equal 'the quick brown fox', @io.read + end + + unless RUBY_VERSION < '1.9' + def test_read_from_multibyte + utf8 = File.open(File.dirname(__FILE__)+'/multibyte.txt') + binary = StringIO.new("\x86") + @io = CompositeReadIO.new(binary,utf8) + + expect = "\x86\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB\n" + expect.force_encoding('BINARY') if expect.respond_to?(:force_encoding) + assert_equal expect, @io.read + end + end + + def test_partial_read + assert_equal 'the quick', @io.read(9) + end + + def test_partial_read_to_boundary + assert_equal 'the quick ', @io.read(10) + end + + def test_read_with_size_larger_than_available + assert_equal 'the quick brown fox', @io.read(32) + end + + def test_read_into_buffer + buf = '' + @io.read(nil, buf) + assert_equal 'the quick brown fox', buf + end + + def test_multiple_reads + assert_equal 'the ', @io.read(4) + assert_equal 'quic', @io.read(4) + assert_equal 'k br', @io.read(4) + assert_equal 'own ', @io.read(4) + assert_equal 'fox', @io.read(4) + end + + def test_read_after_end + @io.read + assert_equal "", @io.read + end + + def test_read_after_end_with_amount + @io.read(32) + assert_equal nil, @io.read(32) + end + + def test_second_full_read_after_rewinding + @io.read + @io.rewind + assert_equal 'the quick brown fox', @io.read + end + + def test_convert_error + assert_raises(ArgumentError) { + UploadIO.convert!('tmp.txt', 'text/plain', 'tmp.txt', 'tmp.txt') + } + end + + ## FIXME excluding on JRuby due to + ## http://jira.codehaus.org/browse/JRUBY-7109 + if IO.respond_to?(:copy_stream) && !defined?(JRUBY_VERSION) + def test_compatible_with_copy_stream + target_io = StringIO.new + Timeout.timeout(1) do + IO.copy_stream(@io, target_io) + end + assert_equal "the quick brown fox", target_io.string + end + end + + def test_empty + io = CompositeReadIO.new + assert_equal "", io.read + end + + def test_empty_limited + io = CompositeReadIO.new + assert_nil io.read(1) + end + + def test_empty_parts + io = CompositeReadIO.new(StringIO.new, StringIO.new('the '), StringIO.new, StringIO.new('quick')) + assert_equal "the", io.read(3) + assert_equal " qu", io.read(3) + assert_equal "ick", io.read(4) + end + + def test_all_empty_parts + io = CompositeReadIO.new(StringIO.new, StringIO.new) + assert_nil io.read(1) + end +end diff --git a/.gems/gems/multipart-post-2.0.0/test/test_parts.rb b/.gems/gems/multipart-post-2.0.0/test/test_parts.rb new file mode 100644 index 0000000..33c1e39 --- /dev/null +++ b/.gems/gems/multipart-post-2.0.0/test/test_parts.rb @@ -0,0 +1,86 @@ +#-- +# Copyright (c) 2007-2012 Nick Sieger. +# See the file README.txt included with the distribution for +# software license details. +#++ + +require 'test/unit' + +require 'parts' +require 'stringio' +require 'composite_io' +require 'tempfile' + + +MULTIBYTE = File.dirname(__FILE__)+'/multibyte.txt' +TEMP_FILE = "temp.txt" + +module AssertPartLength + def assert_part_length(part) + bytes = part.to_io.read + bytesize = bytes.respond_to?(:bytesize) ? bytes.bytesize : bytes.length + assert_equal bytesize, part.length + end +end + +class PartTest < Test::Unit::TestCase + def setup + @string_with_content_type = Class.new(String) do + def content_type; 'application/data'; end + end + end + + def test_file_with_upload_io + assert Parts::Part.file?(UploadIO.new(__FILE__, "text/plain")) + end + + def test_file_with_modified_string + assert !Parts::Part.file?(@string_with_content_type.new("Hello")) + end + + def test_new_with_modified_string + assert_kind_of Parts::ParamPart, + Parts::Part.new("boundary", "multibyte", @string_with_content_type.new("Hello")) + end +end + +class FilePartTest < Test::Unit::TestCase + include AssertPartLength + + def setup + File.open(TEMP_FILE, "w") {|f| f << "1234567890"} + io = UploadIO.new(TEMP_FILE, "text/plain") + @part = Parts::FilePart.new("boundary", "afile", io) + end + + def teardown + File.delete(TEMP_FILE) rescue nil + end + + def test_correct_length + assert_part_length @part + end + + def test_multibyte_file_length + assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(MULTIBYTE, "text/plain")) + end + + def test_multibyte_filename + name = File.read(MULTIBYTE, 300) + file = Tempfile.new(name.respond_to?(:force_encoding) ? name.force_encoding("UTF-8") : name) + assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(file, "text/plain")) + file.close + end +end + +class ParamPartTest < Test::Unit::TestCase + include AssertPartLength + + def setup + @part = Parts::ParamPart.new("boundary", "multibyte", File.read(MULTIBYTE)) + end + + def test_correct_length + assert_part_length @part + end +end diff --git a/.gems/gems/naught-1.0.0/.gitignore b/.gems/gems/naught-1.0.0/.gitignore new file mode 100644 index 0000000..a2890cc --- /dev/null +++ b/.gems/gems/naught-1.0.0/.gitignore @@ -0,0 +1,23 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +/naught.org +/naught.html +/bin +/TAGS +/gems.tags +/tags diff --git a/.gems/gems/naught-1.0.0/.rspec b/.gems/gems/naught-1.0.0/.rspec new file mode 100644 index 0000000..0912718 --- /dev/null +++ b/.gems/gems/naught-1.0.0/.rspec @@ -0,0 +1,2 @@ +--color +--order random diff --git a/.gems/gems/naught-1.0.0/.rubocop.yml b/.gems/gems/naught-1.0.0/.rubocop.yml new file mode 100644 index 0000000..80bed14 --- /dev/null +++ b/.gems/gems/naught-1.0.0/.rubocop.yml @@ -0,0 +1,74 @@ +AllCops: + Includes: + - 'Gemfile' + - 'Rakefile' + - 'naught.gemspec' + +# Avoid long parameter lists +ParameterLists: + Max: 4 + CountKeywordArgs: true + +ClassLength: + Max: 144 # TODO: Lower to 100 + +MethodLength: + CountComments: false + Max: 21 # TODO: Lower to 15 + +# Avoid more than `Max` levels of nesting. +BlockNesting: + Max: 2 + +# Align with the style guide. +CollectionMethods: + PreferredMethods: + map: 'collect' + reduce: 'inject' + find: 'detect' + find_all: 'select' + +# Limit line length +LineLength: + Enabled: false + +# Disable documentation checking until a class needs to be documented once +Documentation: + Enabled: false + +# Enforce Ruby 1.8-compatible hash syntax +HashSyntax: + EnforcedStyle: hash_rockets + +# No spaces inside hash literals +SpaceInsideHashLiteralBraces: + EnforcedStyle: no_space + +# Allow dots at the end of lines +DotPosition: + Enabled: false + +# Don't require magic comment at the top of every file +Encoding: + Enabled: false + +EmptyLinesAroundAccessModifier: + Enabled: true + +# Align ends correctly +EndAlignment: + AlignWith: variable + +# Indentation of when/else +CaseIndentation: + IndentWhenRelativeTo: end + IndentOneStep: false + +Lambda: + Enabled: false + +MethodName: + Enabled: false + +ClassVars: + Enabled: false diff --git a/.gems/gems/naught-1.0.0/.travis.yml b/.gems/gems/naught-1.0.0/.travis.yml new file mode 100644 index 0000000..3cc61e6 --- /dev/null +++ b/.gems/gems/naught-1.0.0/.travis.yml @@ -0,0 +1,20 @@ +before_install: + - gem update --system 2.1.11 + - gem --version +bundler_args: --without development +language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0.0 + - 2.1.0 + - jruby + - jruby-head + - rbx + - ruby-head +matrix: + allow_failures: + - rvm: jruby-head + - rvm: ruby-head + fast_finish: true diff --git a/.gems/gems/naught-1.0.0/Changelog.md b/.gems/gems/naught-1.0.0/Changelog.md new file mode 100644 index 0000000..352617d --- /dev/null +++ b/.gems/gems/naught-1.0.0/Changelog.md @@ -0,0 +1,12 @@ +## 1.0.0 + + - [Replace `::BasicObject` with `Naught::BasicObject`](https://github.com/avdi/naught/commit/8defad0bf9eb65e33054bf0a6e9c625c87c3e6df) + - [Delegate explicit conversions to nil instead of defining them explicitly](https://github.com/avdi/naught/commit/85c195de80ed56993b88f47e09112c903a92a167) + - Add support for (and run tests on) Ruby 1.8, 1.9, 2.0, 2.1, JRuby, and Rubinius + +## 0.0.3 + +Features: + + - New "pebble" mode (Guilherme Carvalho) + diff --git a/.gems/gems/naught-1.0.0/Gemfile b/.gems/gems/naught-1.0.0/Gemfile new file mode 100644 index 0000000..5e92b9a --- /dev/null +++ b/.gems/gems/naught-1.0.0/Gemfile @@ -0,0 +1,31 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in naught.gemspec +gemspec + +gem 'rake' + +group :development do + platforms :ruby_19, :ruby_20, :ruby_21 do + gem 'guard' + gem 'guard-bundler' + gem 'guard-rspec' + end + gem 'pry' + gem 'pry-rescue' +end + +group :test do + gem 'coveralls', :require => false + gem 'json', :platforms => [:jruby, :rbx, :ruby_18, :ruby_19] + gem 'libnotify' + gem 'mime-types', '~> 1.25', :platforms => [:jruby, :ruby_18] + gem 'rspec', '>= 2.14' + gem 'rubocop', '>= 0.16', :platforms => [:ruby_19, :ruby_20, :ruby_21] +end + +platforms :rbx do + gem 'racc' + gem 'rubinius-coverage', '~> 2.0' + gem 'rubysl', '~> 2.0' +end diff --git a/.gems/gems/naught-1.0.0/Guardfile b/.gems/gems/naught-1.0.0/Guardfile new file mode 100644 index 0000000..24e51c8 --- /dev/null +++ b/.gems/gems/naught-1.0.0/Guardfile @@ -0,0 +1,15 @@ +guard 'bundler' do + watch('Gemfile') + watch(/^.+\.gemspec/) +end + +guard :rspec, cli: '-fs --color --order rand' do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + +guard 'ctags-bundler', emacs: true, src_path: ["lib", "spec/support"] do + watch(/^(lib|spec\/support)\/.*\.rb$/) + watch('Gemfile.lock') +end diff --git a/.gems/gems/naught-1.0.0/LICENSE.txt b/.gems/gems/naught-1.0.0/LICENSE.txt new file mode 100644 index 0000000..ff598db --- /dev/null +++ b/.gems/gems/naught-1.0.0/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 Avdi Grimm + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/naught-1.0.0/README.markdown b/.gems/gems/naught-1.0.0/README.markdown new file mode 100644 index 0000000..868b090 --- /dev/null +++ b/.gems/gems/naught-1.0.0/README.markdown @@ -0,0 +1,436 @@ +[![Build Status](https://travis-ci.org/avdi/naught.png?branch=master)](https://travis-ci.org/avdi/naught) +[![Code Climate](https://codeclimate.com/github/avdi/naught.png)](https://codeclimate.com/github/avdi/naught) +[![Coverage Status](https://coveralls.io/repos/avdi/naught/badge.png?branch=master)](https://coveralls.io/r/avdi/naught?branch=master) +[![Gem Version](https://badge.fury.io/rb/naught.png)](http://badge.fury.io/rb/naught) + +A quick intro to Naught +------------------------- + +#### What's all this now then? + +Naught is a toolkit for building [Null +Objects](http://en.wikipedia.org/wiki/Null_Object_pattern) in Ruby. + +#### What's that supposed to mean? + +Null Objects can make your code more +[confident](http://confidentruby.com). + +Here's a method that's not very sure of itself. + +```ruby +class Geordi + def make_it_so(logger=nil) + logger && logger.info("Reversing the flux phase capacitance!") + logger && logger.info("Bounding a tachyon particle beam off of Data's cat!") + logger && logger.warn("Warning, bogon levels are rising!") + end +end +``` + +Now, observe as we give it a dash of confidence with the Null Object +pattern! + +```ruby +class NullLogger + def debug(*); end + def info(*); end + def warn(*); end + def error(*); end + def fatal(*); end +end + +class Geordi + def make_it_so(logger=NullLogger.new) + logger.info "Reversing the flux phase capacitance!" + logger.info "Bounding a tachyon particle beam off of Data's cat!" + logger.warn "Warning, bogon levels are rising!" + end +end +``` + +By providing a `NullLogger` which implements [some of] the `Logger` +interface as no-op methods, we've gotten rid of those unsightly `&&` +operators. + +#### That was simple enough. Why do I need a library for it? + +You don't! The Null Object pattern is a very simple one at its core. + +#### And yet here we are… + +Yes. While you don't *need* a Null Object library, this one offers some +conveniences you probably won't find elsewhere. + +But there's an even more important reason I wrote this library. In the +immortal last words of James T. Kirk: "It was… *fun!*" + +#### OK, so how do I use this thing? + +Well, what would you like to do? + +#### I dunno, gimme an object that responds to any message with nil + +Sure thing! + +```ruby +require 'naught' + +NullObject = Naught.build + +null = NullObject.new +null.foo # => nil +null.bar # => nil +``` + +#### That was… weird. What's with this "build" business? + +Naught is a *toolkit* for building null object classes. It is not a +one-size-fits-all solution. + +What else can I make for you? + +#### How about a "black hole" null object that supports infinite chaining of methods? + +OK. + +```ruby +require 'naught' + +BlackHole = Naught.build do |config| + config.black_hole +end + +null = BlackHole.new +null.foo # => +null.foo.bar.baz # => +null << "hello" << "world" # => +``` + +#### What's that "config" thing? + +That's what you use to customize the generated class to your +liking. Internally, Naught uses the [Builder +Pattern](http://en.wikipedia.org/wiki/Builder_pattern) to make this work.. + +#### Whatever. What if I want a null object that has conversions to Integer, String, etc. using sensible conversions to "zero values"? + +We can do that. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.define_explicit_conversions +end + +null = NullObject.new + +null.to_s # => "" +null.to_i # => 0 +null.to_f # => 0.0 +null.to_a # => [] +null.to_h # => {} +null.to_c # => (0+0i) +null.to_r # => (0/1) +``` + +#### Ah, but what about implicit conversions such as `#to_str`? Like what if I want a null object that implicitly splats the same way as an empty array? + +Gotcha covered. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.define_implicit_conversions +end + +null = NullObject.new + +null.to_str # => "" +null.to_ary # => [] + +a, b, c = [] +a # => nil +b # => nil +c # => nil +x, y, z = null +x # => nil +y # => nil +z # => nil +``` + +#### How about a null object that only stubs out the methods from a specific class? + +That's what `mimic` is for. + +```ruby +require 'naught' + +NullIO = Naught.build do |config| + config.mimic IO +end + +null_io = NullIO.new + +null_io << "foo" # => nil +null_io.readline # => nil +null_io.foobar # => +# ~> -:11:in `
': undefined method `foobar' for +# :NullIO (NoMethodError) +``` + +There is also `impersonate` which takes `mimic` one step further. The +generated null class will be derived from the impersonated class. This +is handy when refitting legacy code that contains type checks. + +```ruby +require 'naught' + +NullIO = Naught.build do |config| + config.impersonate IO +end + +null_io = NullIO.new +IO === null_io # => true + +case null_io +when IO + puts "Yep, checks out!" + null_io << "some output" +else + raise "Hey, I expected an IO!" +end +# >> Yep, checks out! +``` + +#### What about predicate methods? You know, the ones that end with question marks? Shouldn't they return `false` instead of `nil`? + +Sure, if you'd like. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.predicates_return false +end + +null = NullObject.new +null.foo # => nil +null.bar? # => false +null.nil? # => false +``` + +#### Alright smartypants. What if I want to add my own methods? + +Not a problem, just define them in the `.build` block. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.define_explicit_conversions + config.predicates_return false + def to_path + "/dev/null" + end + + # You can override methods generated by Naught + def to_s + "NOTHING TO SEE HERE MOVE ALONG" + end + + def nil? + true + end +end + +null = NullObject.new +null.to_path # => "/dev/null" +null.to_s # => "NOTHING TO SEE HERE MOVE ALONG" +null.nil? # => true +``` + +#### Got anything else up your sleeve? + +Well, we can make the null class a singleton, since null objects +generally have no state. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.singleton +end + +null = NullObject.instance + +null.__id__ # => 17844080 +NullObject.instance.__id__ # => 17844080 +NullObject.new # => +# ~> -:11:in `
': private method `new' called for +# NullObject:Class (NoMethodError) +``` + +Speaking of null objects with state, we can also enable tracing. This is +handy for playing "where'd that null come from?!" Try doing *that* with +`nil`! + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + config.traceable +end + +null = NullObject.new # line 7 + +null.__file__ # => "example.rb" +null.__line__ # => 7 +``` + +We can even conditionally enable either singleton mode (for production) +or tracing (for development). Here's an example of using the `$DEBUG` +global variable (set with the `-d` option to ruby) to choose which one. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + if $DEBUG + config.traceable + else + config.singleton + end +end +``` + +The only caveat is that when swapping between singleton and +non-singleton implementations, you should be careful to always +instantiate your null objects with `NullObject.get`, not `.new` or +`.instance`. `.get` will work whether the class is implemented as a +singleton or not. + +```ruby +NullObject.get # => +``` + +#### And if I want to know legacy code better? + +Naught can make a null object behave as a pebble object. + +```ruby +require 'naught' + +NullObject = Naught.build do |config| + if $DEBUG + config.pebble + else + config.black_hole + end +end +``` + +Now you can pass the pebble object to your code and see which messages are sent to the pebble. + +```ruby +null = NullObject.new + +class MyConsumer < Struct.new(:producer) + def consume + producer.produce + end +end + +MyConsumer.new(null).consume +# >> produce() from consume +# => +``` + +#### Are you done yet? + +Just one more thing. For maximum convenience, Naught-generated null +classes also come with a full suite of conversion functions which can be +included into your classes. + +```ruby +require 'naught' + +NullObject = Naught.build + +include NullObject::Conversions + +# Convert nil to null objects. Everything else passes through. +Maybe(42) # => 42 +Maybe(nil) # => +Maybe(NullObject.get) # => +Maybe{ 42 } # => 42 + +# Insist on a non-null (or nil) value +Just(42) # => 42 +Just(nil) rescue $! # => # +Just(NullObject.get) rescue $! # => #> + +# nils and nulls become nulls. Everything else is rejected. +Null() # => +Null(42) rescue $! # => # +Null(nil) # => +Null(NullObject.get) # => + +# Convert nulls back to nils. Everything else passes through. Useful +# for preventing null objects from "leaking" into public API return +# values. +Actual(42) # => 42 +Actual(nil) # => nil +Actual(NullObject.get) # => nil +Actual { 42 } # => 42 +``` + +Installation +-------------- + +``` {.example} +gem install naught +``` + +Requirements +-------------- + +- Ruby 1.9 + +Contributing +-------------- + +- Fork, branch, submit PR, blah blah blah. Don't forget tests. + +Who's responsible +------------------- + +Naught is by [Avdi Grimm](http://devblog.avdi.org/). + +Prior Art +--------- + +This isn't the first Ruby Null Object library. Others to check out include: + + - [NullAndVoid](https://github.com/jfelchner/null_and_void) + - [BlankSlate](https://github.com/saturnflyer/blank_slate) + + +Further reading +----------------- + +- [Null Object: Something for + Nothing](http://www.two-sdg.demon.co.uk/curbralan/papers/europlop/NullObject.pdf) + (PDF) by Kevlin Henney +- [The Null Object + Pattern](http://www.cs.oberlin.edu/~jwalker/refs/woolf.ps) (PS) by + Bobby Woolf +- [NullObject](http://www.c2.com/cgi/wiki?NullObject) on WikiWiki +- [Null Object + pattern](http://en.wikipedia.org/wiki/Null_Object_pattern) on + Wikipedia +- [Null Objects and + Falsiness](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/), + by Avdi Grimm diff --git a/.gems/gems/naught-1.0.0/Rakefile b/.gems/gems/naught-1.0.0/Rakefile new file mode 100644 index 0000000..8d0de35 --- /dev/null +++ b/.gems/gems/naught-1.0.0/Rakefile @@ -0,0 +1,15 @@ +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +begin + require 'rubocop/rake_task' + Rubocop::RakeTask.new +rescue LoadError + task :rubocop do + $stderr.puts 'Rubocop is disabled' + end +end + +task :default => [:spec, :rubocop] diff --git a/.gems/gems/naught-1.0.0/lib/naught.rb b/.gems/gems/naught-1.0.0/lib/naught.rb new file mode 100644 index 0000000..27ec154 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught.rb @@ -0,0 +1,13 @@ +require 'naught/version' +require 'naught/null_class_builder' +require 'naught/null_class_builder/commands' + +module Naught + def self.build(&customization_block) + builder = NullClassBuilder.new + builder.customize(&customization_block) + builder.generate_class + end + module NullObjectTag + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/basic_object.rb b/.gems/gems/naught-1.0.0/lib/naught/basic_object.rb new file mode 100644 index 0000000..fc0c0ac --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/basic_object.rb @@ -0,0 +1,17 @@ +module Naught + if defined? ::BasicObject + class BasicObject < ::BasicObject + end + else + class BasicObject #:nodoc: + keep = %w[ + ! != == __id__ __send__ equal? instance_eval instance_exec + method_missing singleton_method_added singleton_method_removed + singleton_method_undefined + ] + instance_methods.each do |method_name| + undef_method(method_name) unless keep.include?(method_name) + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/conversions.rb b/.gems/gems/naught-1.0.0/lib/naught/conversions.rb new file mode 100644 index 0000000..a90e7db --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/conversions.rb @@ -0,0 +1,55 @@ +module Naught + module Conversions + def self.included(null_class) + unless class_variable_defined?(:@@included) && @@included + @@null_class = null_class + @@null_equivs = null_class::NULL_EQUIVS + @@included = true + end + super + end + + def Null(object = :nothing_passed) + case object + when NullObjectTag + object + when :nothing_passed, *@@null_equivs + @@null_class.get(:caller => caller(1)) + else + fail ArgumentError, "#{object.inspect} is not null!" + end + end + + def Maybe(object = nil) + object = yield if block_given? + case object + when NullObjectTag + object + when *@@null_equivs + @@null_class.get(:caller => caller(1)) + else + object + end + end + + def Just(object = nil) + object = yield if block_given? + case object + when NullObjectTag, *@@null_equivs + fail ArgumentError, "Null value: #{object.inspect}" + else + object + end + end + + def Actual(object = nil) + object = yield if block_given? + case object + when NullObjectTag + nil + else + object + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder.rb new file mode 100644 index 0000000..a137d09 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder.rb @@ -0,0 +1,186 @@ +require 'naught/basic_object' +require 'naught/conversions' + +module Naught + class NullClassBuilder + # make sure this module exists + module Commands + end + + attr_accessor :base_class, :inspect_proc, :interface_defined + + def initialize + @interface_defined = false + @base_class = Naught::BasicObject + @inspect_proc = lambda { '' } + @stub_strategy = :stub_method_returning_nil + define_basic_methods + end + + def interface_defined? + !!@interface_defined + end + + def customize(&customization_block) + return unless customization_block + customization_module.module_exec(self, &customization_block) + end + + def customization_module + @customization_module ||= Module.new + end + + def null_equivalents + @null_equivalents ||= [nil] + end + + def generate_class + respond_to_any_message unless interface_defined? + generation_mod = Module.new + customization_mod = customization_module # get a local binding + builder = self + + apply_operations(operations, generation_mod) + + null_class = Class.new(@base_class) do + const_set :GeneratedMethods, generation_mod + const_set :Customizations, customization_mod + const_set :NULL_EQUIVS, builder.null_equivalents + include Conversions + remove_const :NULL_EQUIVS + Conversions.instance_methods.each do |instance_method| + undef_method(instance_method) + end + const_set :Conversions, Conversions + + include NullObjectTag + include generation_mod + include customization_mod + end + + apply_operations(class_operations, null_class) + + null_class + end + + def method_missing(method_name, *args, &block) + command_name = command_name_for_method(method_name) + if Commands.const_defined?(command_name) + command_class = Commands.const_get(command_name) + command_class.new(self, *args, &block).call + else + super + end + end + + if RUBY_VERSION >= '1.9' + def respond_to_missing?(method_name, include_private = false) + command_name = command_name_for_method(method_name) + Commands.const_defined?(command_name) || super + rescue NameError + super + end + else + def respond_to?(method_name, include_private = false) + command_name = command_name_for_method(method_name) + Commands.const_defined?(command_name) || super + rescue NameError + super + end + end + + ############################################################################ + # Builder API + # + # See also the contents of lib/naught/null_class_builder/commands + ############################################################################ + + def black_hole + @stub_strategy = :stub_method_returning_self + end + + def respond_to_any_message + defer(:prepend => true) do |subject| + subject.module_eval do + def respond_to?(*) + true + end + end + stub_method(subject, :method_missing) + end + @interface_defined = true + end + + def defer(options = {}, &deferred_operation) + list = options[:class] ? class_operations : operations + if options[:prepend] + list.unshift(deferred_operation) + else + list << deferred_operation + end + end + + def stub_method(subject, name) + send(@stub_strategy, subject, name) + end + + private + + def define_basic_methods + define_basic_instance_methods + define_basic_class_methods + end + + def apply_operations(operations, module_or_class) + operations.each do |operation| + operation.call(module_or_class) + end + end + + def define_basic_instance_methods + defer do |subject| + subject.module_exec(@inspect_proc) do |inspect_proc| + define_method(:inspect, &inspect_proc) + def initialize(*) + end + end + end + end + + def define_basic_class_methods + defer(:class => true) do |subject| + subject.module_eval do + class << self + alias_method :get, :new + end + klass = self + define_method(:class) { klass } + end + end + end + + def class_operations + @class_operations ||= [] + end + + def operations + @operations ||= [] + end + + def stub_method_returning_nil(subject, name) + subject.module_eval do + define_method(name) { |*| nil } + end + end + + def stub_method_returning_self(subject, name) + subject.module_eval do + define_method(name) { |*| self } + end + end + + def command_name_for_method(method_name) + method_name.to_s.gsub(/(?:^|_)([a-z])/) { Regexp.last_match[1].upcase } + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/command.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/command.rb new file mode 100644 index 0000000..72b3b22 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/command.rb @@ -0,0 +1,20 @@ +module Naught + class NullClassBuilder + class Command + attr_reader :builder + + def initialize(builder) + @builder = builder + end + + def call + fail NotImplementedError, + 'Method #call should be overriden in child classes' + end + + def defer(options = {}, &block) + @builder.defer(options, &block) + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands.rb new file mode 100644 index 0000000..e26eb36 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands.rb @@ -0,0 +1,8 @@ +require 'naught/null_class_builder/commands/define_explicit_conversions' +require 'naught/null_class_builder/commands/define_implicit_conversions' +require 'naught/null_class_builder/commands/pebble' +require 'naught/null_class_builder/commands/predicates_return' +require 'naught/null_class_builder/commands/singleton' +require 'naught/null_class_builder/commands/traceable' +require 'naught/null_class_builder/commands/mimic' +require 'naught/null_class_builder/commands/impersonate' diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_explicit_conversions.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_explicit_conversions.rb new file mode 100644 index 0000000..7a45557 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_explicit_conversions.rb @@ -0,0 +1,15 @@ +require 'forwardable' +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class DefineExplicitConversions < ::Naught::NullClassBuilder::Command + def call + defer do |subject| + subject.module_eval do + extend Forwardable + def_delegators :nil, :to_a, :to_c, :to_f, :to_h, :to_i, :to_r, :to_s + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_implicit_conversions.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_implicit_conversions.rb new file mode 100644 index 0000000..8d352e2 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/define_implicit_conversions.rb @@ -0,0 +1,19 @@ +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class DefineImplicitConversions < ::Naught::NullClassBuilder::Command + def call + defer do |subject| + subject.module_eval do + def to_ary + [] + end + + def to_str + '' + end + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/impersonate.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/impersonate.rb new file mode 100644 index 0000000..11c3840 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/impersonate.rb @@ -0,0 +1,8 @@ +module Naught::NullClassBuilder::Commands + class Impersonate < Naught::NullClassBuilder::Commands::Mimic + def initialize(builder, class_to_impersonate, options = {}) + super + builder.base_class = class_to_impersonate + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/mimic.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/mimic.rb new file mode 100644 index 0000000..1156ebd --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/mimic.rb @@ -0,0 +1,37 @@ +require 'naught/basic_object' +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class Mimic < Naught::NullClassBuilder::Command + attr_reader :class_to_mimic, :include_super + + def initialize(builder, class_to_mimic, options = {}) + super(builder) + + @class_to_mimic = class_to_mimic + @include_super = options.fetch(:include_super) { true } + + builder.base_class = root_class_of(class_to_mimic) + builder.inspect_proc = lambda { "" } + builder.interface_defined = true + end + + def call + defer do |subject| + methods_to_stub.each do |method_name| + builder.stub_method(subject, method_name) + end + end + end + + private + + def root_class_of(klass) + klass.ancestors.include?(Object) ? Object : Naught::BasicObject + end + + def methods_to_stub + class_to_mimic.instance_methods(include_super) - Object.instance_methods + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/pebble.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/pebble.rb new file mode 100644 index 0000000..c6eb8ff --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/pebble.rb @@ -0,0 +1,34 @@ +require 'naught/null_class_builder/command' + +module Naught + class NullClassBuilder + module Commands + class Pebble < ::Naught::NullClassBuilder::Command + def initialize(builder, output = $stdout) + @builder = builder + @output = output + end + + def call + defer do |subject| + subject.module_exec(@output) do |output| + + define_method(:method_missing) do |method_name, *args, &block| + pretty_args = args.collect(&:inspect).join(', ').gsub("\"", "'") + output.puts "#{method_name}(#{pretty_args}) from #{parse_caller}" + self + end + + def parse_caller + caller = Kernel.caller(2).first + method_name = caller.match(/\`([\w\s]+(\(\d+\s\w+\))?[\w\s]*)/) + method_name ? method_name[1] : caller + end + private :parse_caller + end + end + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/predicates_return.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/predicates_return.rb new file mode 100644 index 0000000..6952e58 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/predicates_return.rb @@ -0,0 +1,46 @@ +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class PredicatesReturn < Naught::NullClassBuilder::Command + def initialize(builder, return_value) + super(builder) + @predicate_return_value = return_value + end + + def call + defer do |subject| + define_method_missing(subject) + define_predicate_methods(subject) + end + end + + private + + def define_method_missing(subject) + subject.module_exec(@predicate_return_value) do |return_value| + if subject.method_defined?(:method_missing) + original_method_missing = instance_method(:method_missing) + define_method(:method_missing) do |method_name, *args, &block| + if method_name.to_s.end_with?('?') + return_value + else + original_method_missing.bind(self).call(method_name, *args, &block) + end + end + end + end + end + + def define_predicate_methods(subject) + subject.module_exec(@predicate_return_value) do |return_value| + instance_methods.each do |method_name| + if method_name.to_s.end_with?('?') + define_method(method_name) do |*| + return_value + end + end + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/singleton.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/singleton.rb new file mode 100644 index 0000000..44a1b5b --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/singleton.rb @@ -0,0 +1,24 @@ +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class Singleton < Naught::NullClassBuilder::Command + def call + defer(:class => true) do |subject| + require 'singleton' + subject.module_eval do + include ::Singleton + + def self.get(*) + instance + end + + %w(dup clone).each do |method_name| + define_method method_name do + self + end + end + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/traceable.rb b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/traceable.rb new file mode 100644 index 0000000..93a7485 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/null_class_builder/commands/traceable.rb @@ -0,0 +1,20 @@ +require 'naught/null_class_builder/command' + +module Naught::NullClassBuilder::Commands + class Traceable < Naught::NullClassBuilder::Command + def call + defer do |subject| + subject.module_eval do + attr_reader :__file__, :__line__ + + def initialize(options = {}) + range = (RUBY_VERSION.to_f == 1.9 && RUBY_PLATFORM != 'java') ? 4 : 3 + backtrace = options.fetch(:caller) { Kernel.caller(range) } + @__file__, line, _ = backtrace[0].split(':') + @__line__ = line.to_i + end + end + end + end + end +end diff --git a/.gems/gems/naught-1.0.0/lib/naught/version.rb b/.gems/gems/naught-1.0.0/lib/naught/version.rb new file mode 100644 index 0000000..c2efe33 --- /dev/null +++ b/.gems/gems/naught-1.0.0/lib/naught/version.rb @@ -0,0 +1,3 @@ +module Naught + VERSION = '1.0.0' +end diff --git a/.gems/gems/naught-1.0.0/naught.gemspec b/.gems/gems/naught-1.0.0/naught.gemspec new file mode 100644 index 0000000..3a53542 --- /dev/null +++ b/.gems/gems/naught-1.0.0/naught.gemspec @@ -0,0 +1,22 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'naught/version' + +Gem::Specification.new do |spec| + spec.name = 'naught' + spec.version = Naught::VERSION + spec.authors = ['Avdi Grimm'] + spec.email = ['avdi@avdi.org'] + spec.description = %q{Naught is a toolkit for building Null Objects} + spec.summary = spec.description + spec.homepage = 'https://github.com/avdi/naught' + spec.license = 'MIT' + + spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) + spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) } + spec.test_files = spec.files.grep(/^(test|spec|features)\//) + spec.require_paths = ['lib'] + + spec.add_development_dependency 'bundler', '~> 1.3' +end diff --git a/.gems/gems/naught-1.0.0/spec/base_object_spec.rb b/.gems/gems/naught-1.0.0/spec/base_object_spec.rb new file mode 100644 index 0000000..077a194 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/base_object_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'null object with a custom base class' do + + subject(:null) { custom_base_null_class.new } + + let(:custom_base_null_class) do + Naught.build do |b| + b.base_class = Object + end + end + + it 'respond to base class methods' do + expect(null.methods).to be_an Array + end + + it 'respond to unknown methods' do + expect(null.foo).to be_nil + end + + it 'exposes the default base class choice, for the curious' do + default_base_class = :not_set + Naught.build do |b| + default_base_class = b.base_class + end + expect(default_base_class).to eq(Naught::BasicObject) + end + + describe 'singleton null object' do + subject(:null_instance) { custom_base_singleton_null_class.instance } + + let(:custom_base_singleton_null_class) do + Naught.build do |b| + b.singleton + b.base_class = Object + end + end + + it 'can be cloned' do + expect(null_instance.clone).to be(null_instance) + end + + it 'can be duplicated' do + expect(null_instance.dup).to be(null_instance) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/basic_null_object_spec.rb b/.gems/gems/naught-1.0.0/spec/basic_null_object_spec.rb new file mode 100644 index 0000000..8674831 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/basic_null_object_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'basic null object' do + let(:null_class) { Naught.build } + subject(:null) { null_class.new } + + it 'responds to arbitrary messages and returns nil' do + expect(null.info).to be_nil + expect(null.foobaz).to be_nil + expect(null.to_s).to be_nil + end + + it 'accepts any arguments for any messages' do + null.foobaz(1, 2, 3) + end + + it 'reports that it responds to any message' do + expect(null).to respond_to(:info) + expect(null).to respond_to(:foobaz) + expect(null).to respond_to(:to_s) + end + + it 'can be inspected' do + expect(null.inspect).to eq('') + end + + it 'knows its own class' do + expect(null.class).to eq(null_class) + end + + it 'aliases .new to .get' do + expect(null_class.get.class).to be(null_class) + end + +end diff --git a/.gems/gems/naught-1.0.0/spec/blackhole_spec.rb b/.gems/gems/naught-1.0.0/spec/blackhole_spec.rb new file mode 100644 index 0000000..79d84f2 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/blackhole_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'black hole null object' do + subject(:null) { null_class.new } + let(:null_class) do + Naught.build do |b| + b.black_hole + end + end + + it 'returns self from arbitray method calls' do + expect(null.info).to be(null) + expect(null.foobaz).to be(null) + expect(null << 'bar').to be(null) + end +end diff --git a/.gems/gems/naught-1.0.0/spec/explicit_conversions_spec.rb b/.gems/gems/naught-1.0.0/spec/explicit_conversions_spec.rb new file mode 100644 index 0000000..4a6d387 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/explicit_conversions_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper.rb' + +describe 'explicitly convertable null object' do + let(:null_class) do + Naught.build do |b| + b.define_explicit_conversions + end + end + subject(:null) { null_class.new } + + it 'defines common explicit conversions to return zero values' do + expect(null.to_s).to eq('') + expect(null.to_a).to eq([]) + expect(null.to_i).to eq(0) + expect(null.to_f).to eq(0.0) + if RUBY_VERSION >= '2.0' + expect(null.to_h).to eq({}) + elsif RUBY_VERSION >= '1.9' + expect(null.to_c).to eq(Complex(0)) + expect(null.to_r).to eq(Rational(0)) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/functions/actual_spec.rb b/.gems/gems/naught-1.0.0/spec/functions/actual_spec.rb new file mode 100644 index 0000000..72e5033 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/functions/actual_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Actual()' do + include ConvertableNull::Conversions + + specify 'given a null object, returns nil' do + null = ConvertableNull.get + expect(Actual(null)).to be_nil + end + + specify 'given anything else, returns the input unchanged' do + expect(Actual(false)).to be(false) + str = 'hello' + expect(Actual(str)).to be(str) + expect(Actual(nil)).to be_nil + end + + it 'also works with blocks' do + expect(Actual { ConvertableNull.new }).to be_nil + expect(Actual { 'foo' }).to eq('foo') + end +end diff --git a/.gems/gems/naught-1.0.0/spec/functions/just_spec.rb b/.gems/gems/naught-1.0.0/spec/functions/just_spec.rb new file mode 100644 index 0000000..9863817 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/functions/just_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Just()' do + include ConvertableNull::Conversions + + specify 'passes non-nullish values through' do + expect(Just(false)).to be(false) + str = 'hello' + expect(Just(str)).to be(str) + end + + specify 'rejects nullish values' do + expect { Just(nil) }.to raise_error(ArgumentError) + expect { Just('') }.to raise_error(ArgumentError) + expect { Just(ConvertableNull.get) }.to raise_error(ArgumentError) + end + + it 'also works with blocks' do + expect { Just { nil }.class }.to raise_error(ArgumentError) + expect(Just { 'foo' }).to eq('foo') + end +end diff --git a/.gems/gems/naught-1.0.0/spec/functions/maybe_spec.rb b/.gems/gems/naught-1.0.0/spec/functions/maybe_spec.rb new file mode 100644 index 0000000..ae918d2 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/functions/maybe_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'Maybe()' do + include ConvertableNull::Conversions + + specify 'given nil, returns a null object' do + expect(Maybe(nil).class).to be(ConvertableNull) + end + + specify 'given a null object, returns the same null object' do + null = ConvertableNull.get + expect(Maybe(null)).to be(null) + end + + specify 'given anything in null_equivalents, return a null object' do + expect(Maybe('').class).to be(ConvertableNull) + end + + specify 'given anything else, returns the input unchanged' do + expect(Maybe(false)).to be(false) + str = 'hello' + expect(Maybe(str)).to be(str) + end + + it 'generates null objects with useful trace info' do + null, line = Maybe(), __LINE__ + expect(null.__file__).to eq(__FILE__) + expect(null.__line__).to eq(line) + end + + it 'also works with blocks' do + expect(Maybe { nil }.class).to eq(ConvertableNull) + expect(Maybe { 'foo' }).to eq('foo') + end +end diff --git a/.gems/gems/naught-1.0.0/spec/functions/null_spec.rb b/.gems/gems/naught-1.0.0/spec/functions/null_spec.rb new file mode 100644 index 0000000..417e4d1 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/functions/null_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe 'Null()' do + include ConvertableNull::Conversions + + specify 'given no input, returns a null object' do + expect(Null().class).to be(ConvertableNull) + end + + specify 'given nil, returns a null object' do + expect(Null(nil).class).to be(ConvertableNull) + end + + specify 'given a null object, returns the same null object' do + null = ConvertableNull.get + expect(Null(null)).to be(null) + end + + specify 'given anything in null_equivalents, return a null object' do + expect(Null('').class).to be(ConvertableNull) + end + + specify 'given anything else, raises an ArgumentError' do + expect { Null(false) }.to raise_error(ArgumentError) + expect { Null('hello') }.to raise_error(ArgumentError) + end + + it 'generates null objects with useful trace info' do + null, line = Null(), __LINE__ + expect(null.__file__).to eq(__FILE__) + expect(null.__line__).to eq(line) + end + +end diff --git a/.gems/gems/naught-1.0.0/spec/implicit_conversions_spec.rb b/.gems/gems/naught-1.0.0/spec/implicit_conversions_spec.rb new file mode 100644 index 0000000..88d4fb2 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/implicit_conversions_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'implicitly convertable null object' do + subject(:null) { null_class.new } + let(:null_class) do + Naught.build do |b| + b.define_implicit_conversions + end + end + it 'implicitly splats the same way an empty array does' do + a, b = null + expect(a).to be_nil + expect(b).to be_nil + end + it 'is implicitly convertable to String' do + expect(instance_eval(null)).to be_nil + end + it 'implicitly converts to an empty array' do + expect(null.to_ary).to eq([]) + end + it 'implicitly converts to an empty string' do + expect(null.to_str).to eq('') + end + +end diff --git a/.gems/gems/naught-1.0.0/spec/mimic_spec.rb b/.gems/gems/naught-1.0.0/spec/mimic_spec.rb new file mode 100644 index 0000000..4a17fbf --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/mimic_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' +require 'logger' + +describe 'null object mimicking a class' do + class User + def login + 'bob' + end + end + + module Authorizable + def authorized_for?(object) + true + end + end + + class LibraryPatron < User + include Authorizable + + def member? + true + end + + def name + 'Bob' + end + + def notify_of_overdue_books(titles) + puts 'Notifying...' + end + end + + subject(:null) { mimic_class.new } + let(:mimic_class) do + Naught.build do |b| + b.mimic LibraryPatron + end + end + it 'responds to all methods defined on the target class' do + expect(null.member?).to be_nil + expect(null.name).to be_nil + expect(null.notify_of_overdue_books(['The Grapes of Wrath'])).to be_nil + end + + it 'does not respond to methods not defined on the target class' do + expect { null.foobar }.to raise_error(NoMethodError) + end + + it 'reports which messages it does and does not respond to' do + expect(null).to respond_to(:member?) + expect(null).to respond_to(:name) + expect(null).to respond_to(:notify_of_overdue_books) + expect(null).not_to respond_to(:foobar) + end + it 'has an informative inspect string' do + expect(null.inspect).to eq('') + end + + it 'excludes Object methods from being mimicked' do + expect(null.object_id).not_to be_nil + expect(null.hash).not_to be_nil + end + + it 'includes inherited methods' do + expect(null.authorized_for?('something')).to be_nil + expect(null.login).to be_nil + end + + describe 'with include_super: false' do + let(:mimic_class) do + Naught.build do |b| + b.mimic LibraryPatron, :include_super => false + end + end + + it 'excludes inherited methods' do + expect(null).to_not respond_to(:authorized_for?) + expect(null).to_not respond_to(:login) + end + end +end + +describe 'using mimic with black_hole' do + subject(:null) { mimic_class.new } + let(:mimic_class) do + Naught.build do |b| + b.mimic Logger + b.black_hole + end + end + + def self.it_behaves_like_a_black_hole_mimic + it 'returns self from mimicked methods' do + expect(null.info).to equal(null) + expect(null.error).to equal(null) + expect(null << 'test').to equal(null) + end + + it 'does not respond to methods not defined on the target class' do + expect { null.foobar }.to raise_error(NoMethodError) + end + end + + it_behaves_like_a_black_hole_mimic + + describe '(reverse order)' do + let(:mimic_class) do + Naught.build do |b| + b.black_hole + b.mimic Logger + end + end + + it_behaves_like_a_black_hole_mimic + end + +end diff --git a/.gems/gems/naught-1.0.0/spec/naught/null_object_builder/command_spec.rb b/.gems/gems/naught-1.0.0/spec/naught/null_object_builder/command_spec.rb new file mode 100644 index 0000000..0cd749a --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/naught/null_object_builder/command_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +module Naught + describe NullClassBuilder::Command do + it 'is abstract' do + command = NullClassBuilder::Command.new(nil) + expect { command.call }.to raise_error(NotImplementedError) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/naught/null_object_builder_spec.rb b/.gems/gems/naught-1.0.0/spec/naught/null_object_builder_spec.rb new file mode 100644 index 0000000..d639aff --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/naught/null_object_builder_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +module Naught + class NullClassBuilder + module Commands + class TestCommand + end + end + end + + describe NullClassBuilder do + subject(:builder) { NullClassBuilder.new } + it 'responds to commands defined in NullObjectBuilder::Commands' do + expect(builder).to respond_to(:test_command) + end + + it 'translates method calls into command invocations including arguments' do + test_command = double + expect(NullClassBuilder::Commands::TestCommand).to receive(:new). + with(builder, 'foo', 42). + and_return(test_command) + expect(test_command).to receive(:call).and_return('COMMAND RESULT') + expect(builder.test_command('foo', 42)).to eq('COMMAND RESULT') + end + + it 'handles missing non-command missing methods normally' do + expect(builder).not_to respond_to(:nonexistant_method) + expect { builder.nonexistent_method }.to raise_error(NoMethodError) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/naught_spec.rb b/.gems/gems/naught-1.0.0/spec/naught_spec.rb new file mode 100644 index 0000000..caafd04 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/naught_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe 'null object impersonating another type' do + class Point + def x + 23 + end + + def y + 42 + end + end + + subject(:null) { impersonation_class.new } + let(:impersonation_class) do + Naught.build do |b| + b.impersonate Point + end + end + + it 'matches the impersonated type' do + expect(null).to be_a Point + end + + it 'responds to methods from the impersonated type' do + expect(null.x).to be_nil + expect(null.y).to be_nil + end + + it 'does not respond to unknown methods' do + expect { null.foo }.to raise_error(NoMethodError) + end +end + +describe 'traceable null object' do + subject(:trace_null) do + null_object_and_line.first + end + let(:null_object_and_line) do + obj, line = trace_null_class.new, __LINE__ + [obj, line] + end + let(:instantiation_line) { null_object_and_line.last } + let(:trace_null_class) do + Naught.build do |b| + b.traceable + end + end + + it 'remembers the file it was instantiated from' do + expect(trace_null.__file__).to eq(__FILE__) + end + + it 'remembers the line it was instantiated from' do + expect(trace_null.__line__).to eq(instantiation_line) + end + + def make_null + trace_null_class.get(:caller => caller(1)) + end + + it 'can accept custom backtrace info' do + obj, line = make_null, __LINE__ + expect(obj.__line__).to eq(line) + end +end + +describe 'customized null object' do + subject(:custom_null) { custom_null_class.new } + let(:custom_null_class) do + Naught.build do |b| + b.define_explicit_conversions + def to_path + '/dev/null' + end + + def to_s + 'NOTHING TO SEE HERE' + end + end + end + + it 'responds to custom-defined methods' do + expect(custom_null.to_path).to eq('/dev/null') + end + + it 'allows generated methods to be overridden' do + expect(custom_null.to_s).to eq('NOTHING TO SEE HERE') + end +end +TestNull = Naught.build + +describe 'a named null object class' do + it 'has named ancestor modules', :pending => rubinius? do + expect(TestNull.ancestors[0..2].collect(&:name)).to eq([ + 'TestNull', + 'TestNull::Customizations', + 'TestNull::GeneratedMethods' + ]) + end +end diff --git a/.gems/gems/naught-1.0.0/spec/pebble_spec.rb b/.gems/gems/naught-1.0.0/spec/pebble_spec.rb new file mode 100644 index 0000000..caba3ca --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/pebble_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' +require 'stringio' + +describe 'pebble null object' do + class Caller + def call_method(thing) + thing.info + end + + def call_method_inside_block(thing) + 2.times.each { thing.info } + end + + def call_method_inside_nested_block(thing) + 2.times.each { 2.times.each { thing.info } } + end + end + + subject(:null) { null_class.new } + let(:null_class) do + output = test_output # getting local binding + Naught.build do |b| + b.pebble output + end + end + + let(:test_output) { StringIO.new } + + it 'prints the name of the method called' do + expect(test_output).to receive(:puts).with(/^info\(\)/) + null.info + end + + it 'prints the arguments received' do + expect(test_output).to receive(:puts).with(/^info\(\'foo\', 5, \:sym\)/) + null.info('foo', 5, :sym) + end + + it 'prints the name of the caller' do + expect(test_output).to receive(:puts).with(/from call_method$/) + Caller.new.call_method(null) + end + + it 'returns self' do + expect(null.info).to be(null) + end + + context 'when is called from a block' do + it 'prints the indication of a block', + :pending => jruby? || rubinius? || ruby_18? do + expect(test_output).to receive(:puts).twice. + with(/from block/) + Caller.new.call_method_inside_block(null) + end + + it 'prints the name of the method that has the block' do + expect(test_output).to receive(:puts).twice. + with(/call_method_inside_block$/) + Caller.new.call_method_inside_block(null) + end + end + + context 'when is called from many levels blocks' do + it 'prints the indication of blocks and its levels', + :pending => jruby? || rubinius? || ruby_18? do + expect(test_output).to receive(:puts).exactly(4).times. + with(/from block \(2 levels\)/) + Caller.new.call_method_inside_nested_block(null) + end + + it 'prints the name of the method that has the block' do + expect(test_output).to receive(:puts).exactly(4).times. + with(/call_method_inside_nested_block$/) + Caller.new.call_method_inside_nested_block(null) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/predicate_spec.rb b/.gems/gems/naught-1.0.0/spec/predicate_spec.rb new file mode 100644 index 0000000..132b36b --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/predicate_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe 'a null object with predicates_return(false)' do + subject(:null) { null_class.new } + let(:null_class) do + Naught.build do |config| + config.predicates_return false + end + end + + it 'responds to predicate-style methods with false' do + expect(null.too_much_coffee?).to eq(false) + end + + it 'responds to other methods with nil' do + expect(null.foobar).to eq(nil) + end + + describe '(black hole)' do + let(:null_class) do + Naught.build do |config| + config.black_hole + config.predicates_return false + end + end + + it 'responds to predicate-style methods with false' do + expect(null.too_much_coffee?).to eq(false) + end + + it 'responds to other methods with self' do + expect(null.foobar).to be(null) + end + end + + describe '(black hole, reverse order config)' do + let(:null_class) do + Naught.build do |config| + config.predicates_return false + config.black_hole + end + end + + it 'responds to predicate-style methods with false' do + expect(null.too_much_coffee?).to eq(false) + end + + it 'responds to other methods with self' do + expect(null.foobar).to be(null) + end + end + + class Coffee + def black? + true + end + + def origin + 'Ethiopia' + end + end + + describe '(mimic)' do + let(:null_class) do + Naught.build do |config| + config.mimic Coffee + config.predicates_return false + end + end + + it 'responds to predicate-style methods with false' do + expect(null.black?).to eq(false) + end + + it 'responds to other methods with nil' do + expect(null.origin).to be(nil) + end + + it 'does not respond to undefined methods' do + expect(null).not_to respond_to(:leaf_variety) + expect { null.leaf_variety }.to raise_error(NoMethodError) + end + end +end diff --git a/.gems/gems/naught-1.0.0/spec/singleton_null_object_spec.rb b/.gems/gems/naught-1.0.0/spec/singleton_null_object_spec.rb new file mode 100644 index 0000000..0cc9a3f --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/singleton_null_object_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'singleton null object' do + subject(:null_class) do + Naught.build do |b| + b.singleton + end + end + + it 'does not respond to .new' do + expect { null_class.new }.to raise_error + end + + it 'has only one instance' do + null1 = null_class.instance + null2 = null_class.instance + expect(null1).to be(null2) + end + + it 'can be cloned' do + null = null_class.instance + expect(null.clone).to be(null) + end + + it 'can be duplicated' do + null = null_class.instance + expect(null.dup).to be(null) + end + it 'aliases .instance to .get' do + expect(null_class.get).to be null_class.instance + end + it 'permits arbitrary arguments to be passed to .get' do + null_class.get(42, :foo => 'bar') + end +end diff --git a/.gems/gems/naught-1.0.0/spec/spec_helper.rb b/.gems/gems/naught-1.0.0/spec/spec_helper.rb new file mode 100644 index 0000000..e58b711 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/spec_helper.rb @@ -0,0 +1,13 @@ +GEM_ROOT = File.expand_path('../../', __FILE__) +$LOAD_PATH.unshift File.join(GEM_ROOT, 'lib') + +if ENV['TRAVIS'] + require 'coveralls' + Coveralls.wear! +else + require 'simplecov' + SimpleCov.start +end + +require 'naught' +Dir[File.join(GEM_ROOT, 'spec', 'support', '**/*.rb')].each { |f| require f } diff --git a/.gems/gems/naught-1.0.0/spec/support/convertable_null.rb b/.gems/gems/naught-1.0.0/spec/support/convertable_null.rb new file mode 100644 index 0000000..8b8d43b --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/support/convertable_null.rb @@ -0,0 +1,4 @@ +ConvertableNull = Naught.build do |b| + b.null_equivalents << '' + b.traceable +end diff --git a/.gems/gems/naught-1.0.0/spec/support/jruby.rb b/.gems/gems/naught-1.0.0/spec/support/jruby.rb new file mode 100644 index 0000000..5e6db75 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/support/jruby.rb @@ -0,0 +1,3 @@ +def jruby? + RUBY_PLATFORM == 'java' +end diff --git a/.gems/gems/naught-1.0.0/spec/support/rubinius.rb b/.gems/gems/naught-1.0.0/spec/support/rubinius.rb new file mode 100644 index 0000000..949586f --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/support/rubinius.rb @@ -0,0 +1,3 @@ +def rubinius? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' +end diff --git a/.gems/gems/naught-1.0.0/spec/support/ruby_18.rb b/.gems/gems/naught-1.0.0/spec/support/ruby_18.rb new file mode 100644 index 0000000..ba9eae7 --- /dev/null +++ b/.gems/gems/naught-1.0.0/spec/support/ruby_18.rb @@ -0,0 +1,3 @@ +def ruby_18? + RUBY_VERSION.to_f == 1.8 +end diff --git a/.gems/gems/simple_oauth-0.2.0/.gemtest b/.gems/gems/simple_oauth-0.2.0/.gemtest new file mode 100644 index 0000000..e69de29 diff --git a/.gems/gems/simple_oauth-0.2.0/.gitignore b/.gems/gems/simple_oauth-0.2.0/.gitignore new file mode 100644 index 0000000..74d8b53 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/.gitignore @@ -0,0 +1,9 @@ +*.rbc +.bundle +.DS_Store +.yardoc +coverage +doc +Gemfile.lock +pkg +rdoc diff --git a/.gems/gems/simple_oauth-0.2.0/.rspec b/.gems/gems/simple_oauth-0.2.0/.rspec new file mode 100644 index 0000000..0ea59b0 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/.rspec @@ -0,0 +1,3 @@ +--color +--fail-fast +--order random diff --git a/.gems/gems/simple_oauth-0.2.0/.travis.yml b/.gems/gems/simple_oauth-0.2.0/.travis.yml new file mode 100644 index 0000000..0828e27 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/.travis.yml @@ -0,0 +1,10 @@ +language: ruby +rvm: + - rbx-18mode + - rbx-19mode + - jruby-18mode + - jruby-19mode + - 1.8.7 + - 1.9.2 + - 1.9.3 + - ruby-head diff --git a/.gems/gems/simple_oauth-0.2.0/.yardopts b/.gems/gems/simple_oauth-0.2.0/.yardopts new file mode 100644 index 0000000..70bb7f2 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/.yardopts @@ -0,0 +1,4 @@ +--markup markdown +- +HISTORY.md +LICENSE.md diff --git a/.gems/gems/simple_oauth-0.2.0/CONTRIBUTING.md b/.gems/gems/simple_oauth-0.2.0/CONTRIBUTING.md new file mode 100644 index 0000000..c556c97 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/CONTRIBUTING.md @@ -0,0 +1,8 @@ +## Contributing +1. Fork the project. +2. Create a topic branch. +3. Add failing tests. +4. Add code to pass the failing tests. +5. Run `bundle exec rake`. If failing, repeat step 4. +6. Commit and push your changes. +7. Submit a pull request. Please do not include changes to the gemspec. diff --git a/.gems/gems/simple_oauth-0.2.0/Gemfile b/.gems/gems/simple_oauth-0.2.0/Gemfile new file mode 100644 index 0000000..f50abda --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/Gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +platforms :jruby do + gem 'jruby-openssl', '~> 0.7' +end + +gemspec diff --git a/.gems/gems/simple_oauth-0.2.0/LICENSE b/.gems/gems/simple_oauth-0.2.0/LICENSE new file mode 100644 index 0000000..24e2cf9 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010 Steve Richert, Erik Michaels-Ober + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.gems/gems/simple_oauth-0.2.0/README.md b/.gems/gems/simple_oauth-0.2.0/README.md new file mode 100644 index 0000000..a532562 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/README.md @@ -0,0 +1,33 @@ +# simple_oauth [![Build Status](https://secure.travis-ci.org/laserlemon/simple_oauth.png)](http://travis-ci.org/laserlemon/simple_oauth) [![Dependency Status](https://gemnasium.com/laserlemon/simple_oauth.png)](https://gemnasium.com/laserlemon/simple_oauth) + +Simply builds and verifies OAuth headers + +## Supported Rubies +This library aims to support and is [tested +against](http://travis-ci.org/laserlemon/simple_oauth) the following Ruby +implementations: + +* Ruby 1.8.7 +* Ruby 1.9.2 +* Ruby 1.9.3 +* Ruby head +* [JRuby](http://www.jruby.org/) +* [Rubinius](http://rubini.us/) + +If something doesn't work on one of these interpreters, it should be considered +a bug. + +This library may inadvertently work (or seem to work) on other Ruby +implementations, however support will only be provided for the versions listed +above. + +If you would like this library to support another Ruby version, you may +volunteer to be a maintainer. Being a maintainer entails making sure all tests +run and pass on that implementation. When something breaks on your +implementation, you will be personally responsible for providing patches in a +timely fashion. If critical issues for a particular implementation exist at the +time of a major release, support for that Ruby version may be dropped. + +## Copyright +Copyright (c) 2010 Steve Richert, Erik Michaels-Ober. +See [LICENSE](https://github.com/laserlemon/simple_oauth/blob/master/LICENSE) for details. diff --git a/.gems/gems/simple_oauth-0.2.0/Rakefile b/.gems/gems/simple_oauth-0.2.0/Rakefile new file mode 100644 index 0000000..93cb943 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/Rakefile @@ -0,0 +1,6 @@ +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth.rb b/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth.rb new file mode 100644 index 0000000..f2bc580 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth.rb @@ -0,0 +1 @@ +require 'simple_oauth/header' diff --git a/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth/header.rb b/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth/header.rb new file mode 100644 index 0000000..16aa6e4 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/lib/simple_oauth/header.rb @@ -0,0 +1,126 @@ +require 'openssl' +require 'uri' +require 'base64' +require 'cgi' + +module SimpleOAuth + class Header + ATTRIBUTE_KEYS = [:callback, :consumer_key, :nonce, :signature_method, :timestamp, :token, :verifier, :version] unless defined? ::SimpleOAuth::Header::ATTRIBUTE_KEYS + attr_reader :method, :params, :options + + class << self + def default_options + { + :nonce => OpenSSL::Random.random_bytes(16).unpack('H*')[0], + :signature_method => 'HMAC-SHA1', + :timestamp => Time.now.to_i.to_s, + :version => '1.0' + } + end + + def parse(header) + header.to_s.sub(/^OAuth\s/, '').split(/,\s*/).inject({}) do |attributes, pair| + match = pair.match(/^(\w+)\=\"([^\"]*)\"$/) + attributes.merge(match[1].sub(/^oauth_/, '').to_sym => decode(match[2])) + end + end + + def escape(value) + uri_parser.escape(value.to_s, /[^a-z0-9\-\.\_\~]/i) + end + alias encode escape + + def unescape(value) + uri_parser.unescape(value.to_s) + end + alias decode unescape + + private + + def uri_parser + @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI + end + + end + + def initialize(method, url, params, oauth = {}) + @method = method.to_s.upcase + @uri = URI.parse(url.to_s) + @uri.scheme = @uri.scheme.downcase + @uri.normalize! + @uri.fragment = nil + @params = params + @options = oauth.is_a?(Hash) ? self.class.default_options.merge(oauth) : self.class.parse(oauth) + end + + def url + uri = @uri.dup + uri.query = nil + uri.to_s + end + + def to_s + "OAuth #{normalized_attributes}" + end + + def valid?(secrets = {}) + original_options = options.dup + options.merge!(secrets) + valid = options[:signature] == signature + options.replace(original_options) + valid + end + + def signed_attributes + attributes.merge(:oauth_signature => signature) + end + + private + + def normalized_attributes + signed_attributes.sort_by{|k,v| k.to_s }.map{|k,v| %(#{k}="#{self.class.encode(v)}") }.join(', ') + end + + def attributes + ATTRIBUTE_KEYS.inject({}){|a,k| options[k] ? a.merge(:"oauth_#{k}" => options[k]) : a } + end + + def signature + send(options[:signature_method].downcase.tr('-', '_') + '_signature') + end + + def hmac_sha1_signature + Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, secret, signature_base)).chomp.gsub(/\n/, '') + end + + def secret + options.values_at(:consumer_secret, :token_secret).map{|v| self.class.encode(v) }.join('&') + end + alias_method :plaintext_signature, :secret + + def signature_base + [method, url, normalized_params].map{|v| self.class.encode(v) }.join('&') + end + + def normalized_params + signature_params.map{|p| p.map{|v| self.class.encode(v) } }.sort.map{|p| p.join('=') }.join('&') + end + + def signature_params + attributes.to_a + params.to_a + url_params + end + + def url_params + CGI.parse(@uri.query || '').inject([]){|p,(k,vs)| p + vs.sort.map{|v| [k, v] } } + end + + def rsa_sha1_signature + Base64.encode64(private_key.sign(OpenSSL::Digest::SHA1.new, signature_base)).chomp.gsub(/\n/, '') + end + + def private_key + OpenSSL::PKey::RSA.new(options[:consumer_secret]) + end + + end +end diff --git a/.gems/gems/simple_oauth-0.2.0/simple_oauth.gemspec b/.gems/gems/simple_oauth-0.2.0/simple_oauth.gemspec new file mode 100644 index 0000000..d99d926 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/simple_oauth.gemspec @@ -0,0 +1,21 @@ +# encoding: utf-8 + +Gem::Specification.new do |spec| + spec.name = 'simple_oauth' + spec.version = '0.2.0' + + spec.authors = ["Steve Richert", "Erik Michaels-Ober"] + spec.email = ['steve.richert@gmail.com', 'sferik@gmail.com'] + spec.description = 'Simply builds and verifies OAuth headers' + spec.summary = spec.description + spec.homepage = 'https://github.com/laserlemon/simple_oauth' + spec.licenses = ['MIT'] + + spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec', '>= 2' + spec.add_development_dependency 'simplecov' + + spec.files = `git ls-files`.split($\) + spec.test_files = spec.files.grep(/^test\//) + spec.require_paths = ["lib"] +end diff --git a/.gems/gems/simple_oauth-0.2.0/spec/helper.rb b/.gems/gems/simple_oauth-0.2.0/spec/helper.rb new file mode 100644 index 0000000..9be179d --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/spec/helper.rb @@ -0,0 +1,21 @@ +unless ENV['CI'] + require 'simplecov' + SimpleCov.start do + add_filter 'spec' + end +end + +require 'simple_oauth' +require 'rspec' + +def uri_parser + @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI +end + +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = :expect + end +end + +Dir[File.expand_path('../support/**/*.rb', __FILE__)].each{|f| require f } diff --git a/.gems/gems/simple_oauth-0.2.0/spec/simple_oauth/header_spec.rb b/.gems/gems/simple_oauth-0.2.0/spec/simple_oauth/header_spec.rb new file mode 100644 index 0000000..466a6e3 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/spec/simple_oauth/header_spec.rb @@ -0,0 +1,372 @@ +# encoding: utf-8 + +require 'helper' + +describe SimpleOAuth::Header do + describe ".default_options" do + let(:default_options){ SimpleOAuth::Header.default_options } + + it "is different every time" do + expect(SimpleOAuth::Header.default_options).not_to eq default_options + end + + it "is used for new headers" do + SimpleOAuth::Header.stub(:default_options => default_options) + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) + expect(header.options).to eq default_options + end + + it "includes a signature method and an OAuth version" do + expect(default_options[:signature_method]).not_to be_nil + expect(default_options[:version]).not_to be_nil + end + end + + describe ".escape" do + it "escapes (most) non-word characters" do + [' ', '!', '@', '#', '$', '%', '^', '&'].each do |character| + escaped = SimpleOAuth::Header.escape(character) + expect(escaped).not_to eq character + expect(escaped).to eq uri_parser.escape(character, /.*/) + end + end + + it "does not escape - . or ~" do + ['-', '.', '~'].each do |character| + escaped = SimpleOAuth::Header.escape(character) + expect(escaped).to eq character + end + end + + def self.test_special_characters + it "escapes non-ASCII characters" do + expect(SimpleOAuth::Header.escape('é')).to eq '%C3%A9' + end + + it "escapes multibyte characters" do + expect(SimpleOAuth::Header.escape('あ')).to eq '%E3%81%82' + end + end + + if RUBY_VERSION >= '1.9' + test_special_characters + else + %w(n N e E s S u U).each do |kcode| + describe %(when $KCODE = "#{kcode}") do + original_kcode = $KCODE + begin + $KCODE = kcode + test_special_characters + ensure + $KCODE = original_kcode + end + end + end + end + end + + describe ".unescape" do + pending + end + + describe ".parse" do + let(:header){ SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}) } + let(:parsed_options){ SimpleOAuth::Header.parse(header) } + + it "returns a hash" do + expect(parsed_options).to be_a(Hash) + end + + it "includes the options used to build the header" do + expect(parsed_options.reject{|k,_| k == :signature }).to eq header.options + end + + it "includes a signature" do + expect(header.options).not_to have_key(:signature) + expect(parsed_options).to have_key(:signature) + expect(parsed_options[:signature]).not_to be_nil + end + + it "handles optional 'linear white space'" do + parsed_header_with_spaces = SimpleOAuth::Header.parse 'OAuth oauth_consumer_key="abcd", oauth_nonce="oLKtec51GQy", oauth_signature="efgh%26mnop", oauth_signature_method="PLAINTEXT", oauth_timestamp="1286977095", oauth_token="ijkl", oauth_version="1.0"' + expect(parsed_header_with_spaces).to be_a_kind_of(Hash) + expect(parsed_header_with_spaces.keys.size).to eq 7 + + parsed_header_with_tabs = SimpleOAuth::Header.parse 'OAuth oauth_consumer_key="abcd", oauth_nonce="oLKtec51GQy", oauth_signature="efgh%26mnop", oauth_signature_method="PLAINTEXT", oauth_timestamp="1286977095", oauth_token="ijkl", oauth_version="1.0"' + expect(parsed_header_with_tabs).to be_a_kind_of(Hash) + expect(parsed_header_with_tabs.keys.size).to eq 7 + + parsed_header_with_spaces_and_tabs = SimpleOAuth::Header.parse 'OAuth oauth_consumer_key="abcd", oauth_nonce="oLKtec51GQy", oauth_signature="efgh%26mnop", oauth_signature_method="PLAINTEXT", oauth_timestamp="1286977095", oauth_token="ijkl", oauth_version="1.0"' + expect(parsed_header_with_spaces_and_tabs).to be_a_kind_of(Hash) + expect(parsed_header_with_spaces_and_tabs.keys.size).to eq 7 + + parsed_header_without_spaces = SimpleOAuth::Header.parse 'OAuth oauth_consumer_key="abcd",oauth_nonce="oLKtec51GQy",oauth_signature="efgh%26mnop",oauth_signature_method="PLAINTEXT",oauth_timestamp="1286977095",oauth_token="ijkl",oauth_version="1.0"' + expect(parsed_header_without_spaces).to be_a_kind_of(Hash) + expect(parsed_header_without_spaces.keys.size).to eq 7 + end + end + + describe "#initialize" do + let(:header){ SimpleOAuth::Header.new(:get, 'HTTPS://api.TWITTER.com:443/1/statuses/friendships.json?foo=bar#anchor', {}) } + + it "stringifies and uppercases the request method" do + expect(header.method).to eq 'GET' + end + + it "downcases the scheme and authority" do + expect(header.url).to match %r(^https://api\.twitter\.com/) + end + + it "ignores the query and fragment" do + expect(header.url).to match %r(/1/statuses/friendships\.json$) + end + end + + describe "#valid?" do + context "using the HMAC-SHA1 signature method" do + it "requires consumer and token secrets" do + secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'} + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets) + parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header) + expect(parsed_header).not_to be_valid + expect(parsed_header).to be_valid(secrets) + end + end + + context "using the RSA-SHA1 signature method" do + it "requires an identical private key" do + secrets = {:consumer_secret => rsa_private_key} + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'RSA-SHA1')) + parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header) + expect{ parsed_header.valid? }.to raise_error(TypeError) + expect(parsed_header).to be_valid(secrets) + end + end + + context "using the RSA-SHA1 signature method" do + it "requires consumer and token secrets" do + secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'} + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'PLAINTEXT')) + parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header) + expect(parsed_header).not_to be_valid + expect(parsed_header).to be_valid(secrets) + end + end + end + + describe "#normalized_attributes" do + let(:header){ SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}) } + let(:normalized_attributes){ header.send(:normalized_attributes) } + + it "returns a sorted-key, quoted-value and comma-separated list" do + header.stub(:signed_attributes => {:d => 1, :c => 2, :b => 3, :a => 4}) + expect(normalized_attributes).to eq 'a="4", b="3", c="2", d="1"' + end + + it "URI encodes its values" do + header.stub(:signed_attributes => {1 => '!', 2 => '@', 3 => '#', 4 => '$'}) + expect(normalized_attributes).to eq '1="%21", 2="%40", 3="%23", 4="%24"' + end + end + + describe "#signed_attributes" do + it "includes the OAuth signature" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}) + expect(header.send(:signed_attributes)).to have_key(:oauth_signature) + end + end + + describe "#attributes" do + let(:header) do + options = {} + SimpleOAuth::Header::ATTRIBUTE_KEYS.each{|k| options[k] = k.to_s.upcase } + options[:other] = 'OTHER' + SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}, options) + end + let(:attributes){ header.send(:attributes) } + + it "prepends keys with 'oauth_'" do + expect(attributes.keys).to be_all{|k| k.to_s =~ /^oauth_/ } + end + + it "excludes keys not included in the list of valid attributes" do + expect(attributes.keys).to be_all{|k| k.is_a?(Symbol) } + expect(attributes).not_to have_key(:oauth_other) + end + + it "preserves values for valid keys" do + expect(attributes.size).to eq SimpleOAuth::Header::ATTRIBUTE_KEYS.size + expect(attributes).to be_all{|k,v| k.to_s == "oauth_#{v.downcase}" } + end + end + + describe "#signature" do + context "calls the appropriate signature method" do + specify "when using HMAC-SHA1" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'HMAC-SHA1') + header.should_receive(:hmac_sha1_signature).once.and_return('HMAC_SHA1_SIGNATURE') + expect(header.send(:signature)).to eq 'HMAC_SHA1_SIGNATURE' + end + + specify "when using RSA-SHA1" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'RSA-SHA1') + header.should_receive(:rsa_sha1_signature).once.and_return('RSA_SHA1_SIGNATURE') + expect(header.send(:signature)).to eq 'RSA_SHA1_SIGNATURE' + end + + specify "when using PLAINTEXT" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'PLAINTEXT') + header.should_receive(:plaintext_signature).once.and_return('PLAINTEXT_SIGNATURE') + expect(header.send(:signature)).to eq 'PLAINTEXT_SIGNATURE' + end + end + end + + describe "#hmac_sha1_signature" do + it "reproduces a successful Twitter GET" do + options = { + :consumer_key => '8karQBlMg6gFOwcf8kcoYw', + :consumer_secret => '3d0vcHyUiiqADpWxolW8nlDIpSWMlyK7YNgc5Qna2M', + :nonce => '547fed103e122eecf84c080843eedfe6', + :signature_method => 'HMAC-SHA1', + :timestamp => '1286830180', + :token => '201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh', + :token_secret => 'T5qa1tF57tfDzKmpM89DHsNuhgOY4NT6DlNLsTFcuQ' + } + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, options) + expect(header.to_s).to eq 'OAuth oauth_consumer_key="8karQBlMg6gFOwcf8kcoYw", oauth_nonce="547fed103e122eecf84c080843eedfe6", oauth_signature="i9CT6ahDRAlfGX3hKYf78QzXsaw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1286830180", oauth_token="201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh", oauth_version="1.0"' + end + + it "reproduces a successful Twitter POST" do + options = { + :consumer_key => '8karQBlMg6gFOwcf8kcoYw', + :consumer_secret => '3d0vcHyUiiqADpWxolW8nlDIpSWMlyK7YNgc5Qna2M', + :nonce => 'b40a3e0f18590ecdcc0e273f7d7c82f8', + :signature_method => 'HMAC-SHA1', + :timestamp => '1286830181', + :token => '201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh', + :token_secret => 'T5qa1tF57tfDzKmpM89DHsNuhgOY4NT6DlNLsTFcuQ' + } + header = SimpleOAuth::Header.new(:post, 'https://api.twitter.com/1/statuses/update.json', {:status => 'hi, again'}, options) + expect(header.to_s).to eq 'OAuth oauth_consumer_key="8karQBlMg6gFOwcf8kcoYw", oauth_nonce="b40a3e0f18590ecdcc0e273f7d7c82f8", oauth_signature="mPqSFKejrWWk3ZT9bTQjhO5b2xI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1286830181", oauth_token="201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh", oauth_version="1.0"' + end + end + + describe "#secret" do + let(:header){ SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) } + let(:secret){ header.send(:secret) } + + it "combines the consumer and token secrets with an ampersand" do + header.stub(:options => {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'}) + expect(secret).to eq 'CONSUMER_SECRET&TOKEN_SECRET' + end + + it "URI encodes each secret value before combination" do + header.stub(:options => {:consumer_secret => 'CONSUM#R_SECRET', :token_secret => 'TOKEN_S#CRET'}) + expect(secret).to eq 'CONSUM%23R_SECRET&TOKEN_S%23CRET' + end + end + + describe "#signature_base" do + let(:header){ SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) } + let(:signature_base){ header.send(:signature_base) } + + it "combines the request method, URL and normalized parameters using ampersands" do + header.stub(:method => 'METHOD', :url => 'URL', :normalized_params => 'NORMALIZED_PARAMS') + expect(signature_base).to eq 'METHOD&URL&NORMALIZED_PARAMS' + end + + it "URI encodes each value before combination" do + header.stub(:method => 'ME#HOD', :url => 'U#L', :normalized_params => 'NORMAL#ZED_PARAMS') + expect(signature_base).to eq 'ME%23HOD&U%23L&NORMAL%23ZED_PARAMS' + end + end + + describe "#normalized_params" do + let(:header) do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) + header.stub(:signature_params => [['A', '4'], ['B', '3'], ['B', '2'], ['C', '1'], ['D[]', '0 ']]) + header + end + let(:signature_params){ header.send(:signature_params) } + let(:normalized_params){ header.send(:normalized_params) } + + it "joins key/value pairs with equal signs and ampersands" do + expect(normalized_params).to be_a(String) + parts = normalized_params.split('&') + expect(parts.size).to eq signature_params.size + pairs = parts.map{|p| p.split('=') } + expect(pairs).to be_all{|p| p.size == 2 } + end + end + + describe "#signature_params" do + let(:header){ SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) } + let(:signature_params){ header.send(:signature_params) } + + it "combines OAuth header attributes, body parameters and URL parameters into an flattened array of key/value pairs" do + header.stub( + :attributes => {:attribute => 'ATTRIBUTE'}, + :params => {'param' => 'PARAM'}, + :url_params => [['url_param', '1'], ['url_param', '2']] + ) + expect(signature_params).to eq [ + [:attribute, 'ATTRIBUTE'], + ['param', 'PARAM'], + ['url_param', '1'], + ['url_param', '2'] + ] + end + end + + describe "#url_params" do + it "returns an empty array when the URL has no query parameters" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}) + expect(header.send(:url_params)).to eq [] + end + + it "returns an array of key/value pairs for each query parameter" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=TEST', {}) + expect(header.send(:url_params)).to eq [['test', 'TEST']] + end + + it "sorts values for repeated keys" do + header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=3&test=1&test=2', {}) + expect(header.send(:url_params)).to eq [['test', '1'], ['test', '2'], ['test', '3']] + end + end + + describe "#rsa_sha1_signature" do + it "reproduces a successful OAuth example GET" do + options = { + :consumer_key => 'dpf43f3p2l4k3l03', + :consumer_secret => rsa_private_key, + :nonce => '13917289812797014437', + :signature_method => 'RSA-SHA1', + :timestamp => '1196666512' + } + header = SimpleOAuth::Header.new(:get, 'http://photos.example.net/photos', {:file => 'vacaction.jpg', :size => 'original'}, options) + expect(header.to_s).to eq 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="13917289812797014437", oauth_signature="jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D", oauth_signature_method="RSA-SHA1", oauth_timestamp="1196666512", oauth_version="1.0"' + end + end + + describe "#private_key" do + pending + end + + describe "#plaintext_signature" do + it "reproduces a successful OAuth example GET" do + options = { + :consumer_key => 'abcd', + :consumer_secret => 'efgh', + :nonce => 'oLKtec51GQy', + :signature_method => 'PLAINTEXT', + :timestamp => '1286977095', + :token => 'ijkl', + :token_secret => 'mnop' + } + header = SimpleOAuth::Header.new(:get, 'http://host.net/resource?name=value', {:name => 'value'}, options) + expect(header.to_s).to eq 'OAuth oauth_consumer_key="abcd", oauth_nonce="oLKtec51GQy", oauth_signature="efgh%26mnop", oauth_signature_method="PLAINTEXT", oauth_timestamp="1286977095", oauth_token="ijkl", oauth_version="1.0"' + end + end +end diff --git a/.gems/gems/simple_oauth-0.2.0/spec/support/fixtures/rsa-private-key b/.gems/gems/simple_oauth-0.2.0/spec/support/fixtures/rsa-private-key new file mode 100644 index 0000000..e0f5542 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/spec/support/fixtures/rsa-private-key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V +A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d +7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ +hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H +X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm +uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw +rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z +zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn +qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG +WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno +cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ +3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 +AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 +Lw03eHTNQghS0A== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/.gems/gems/simple_oauth-0.2.0/spec/support/rsa.rb b/.gems/gems/simple_oauth-0.2.0/spec/support/rsa.rb new file mode 100644 index 0000000..0d3f116 --- /dev/null +++ b/.gems/gems/simple_oauth-0.2.0/spec/support/rsa.rb @@ -0,0 +1,11 @@ +module RSAHelpers + PRIVATE_KEY_PATH = File.expand_path('../fixtures/rsa-private-key', __FILE__) + + def rsa_private_key + @rsa_private_key ||= File.read(PRIVATE_KEY_PATH) + end +end + +RSpec.configure do |config| + config.include RSAHelpers +end diff --git a/.gems/gems/thread_safe-0.3.4/.travis.yml b/.gems/gems/thread_safe-0.3.4/.travis.yml new file mode 100644 index 0000000..0d087fd --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/.travis.yml @@ -0,0 +1,24 @@ +language: ruby +rvm: + - jruby-18mode + - jruby-19mode + - rbx-2 + - 1.8.7 + - 1.9.3 + - 2.0.0 + - 2.1.0 +jdk: # for JRuby only + - openjdk7 + - oraclejdk8 +matrix: + exclude: + - rvm: rbx-2 + jdk: oraclejdk8 + - rvm: 1.8.7 + jdk: oraclejdk8 + - rvm: 1.9.3 + jdk: oraclejdk8 + - rvm: 2.0.0 + jdk: oraclejdk8 + - rvm: 2.1.0 + jdk: oraclejdk8 \ No newline at end of file diff --git a/.gems/gems/thread_safe-0.3.4/Gemfile b/.gems/gems/thread_safe-0.3.4/Gemfile new file mode 100644 index 0000000..fa65970 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in thread_safe.gemspec +gemspec diff --git a/.gems/gems/thread_safe-0.3.4/LICENSE b/.gems/gems/thread_safe-0.3.4/LICENSE new file mode 100644 index 0000000..5336718 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/LICENSE @@ -0,0 +1,144 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as +defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that +is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that +control, are controlled by, or are under common control with that entity. For the purposes +of this definition, "control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, or (ii) ownership +of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by +this License. + +"Source" form shall mean the preferred form for making modifications, including but not +limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of +a Source form, including but not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available +under the License, as indicated by a copyright notice that is included in or attached to the +work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on +(or derived from) the Work and for which the editorial revisions, annotations, elaborations, +or other modifications represent, as a whole, an original work of authorship. For the +purposes of this License, Derivative Works shall not include works that remain separable +from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works +thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work +and any modifications or additions to that Work or Derivative Works thereof, that is +intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by +an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the +purposes of this definition, "submitted" means any form of electronic, verbal, or written +communication sent to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and issue tracking +systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and +improving the Work, but excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a +Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each +Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such Derivative +Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each +Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license +applies only to those patent claims licensable by such Contributor that are necessarily +infringed by their Contribution(s) alone or by combination of their Contribution(s) with the +Work to which such Contribution(s) was submitted. If You institute patent litigation against +any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or +a Contribution incorporated within the Work constitutes direct or contributory patent +infringement, then any patent licenses granted to You under this License for that Work shall +terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works +thereof in any medium, with or without modifications, and in Source or Object form, provided +that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; +and + +You must cause any modified files to carry prominent notices stating that You changed the +files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all +copyright, patent, trademark, and attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative +Works that You distribute must include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not pertain to any part of the +Derivative Works, in at least one of the following places: within a NOTICE text file +distributed as part of the Derivative Works; within the Source form or documentation, if +provided along with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of the NOTICE +file are for informational purposes only and do not modify the License. You may add Your own +attribution notices within Derivative Works that You distribute, alongside or as an addendum +to the NOTICE text from the Work, provided that such additional attribution notices cannot +be construed as modifying the License. You may add Your own copyright statement to Your +modifications and may provide additional or different license terms and conditions for use, +reproduction, or distribution of Your modifications, or for any such Derivative Works as a +whole, provided Your use, reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution +intentionally submitted for inclusion in the Work by You to the Licensor shall be under the +terms and conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of any +separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for reasonable and +customary use in describing the origin of the Work and reproducing the content of the NOTICE +file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, +Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, +without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for +determining the appropriateness of using or redistributing the Work and assume any risks +associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort +(including negligence), contract, or otherwise, unless required by applicable law (such as +deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, or +consequential damages of any character arising as a result of this License or out of the use +or inability to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial damages or +losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative +Works thereof, You may choose to offer, and charge a fee for, acceptance of support, +warranty, indemnity, or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only on Your own behalf and on +Your sole responsibility, not on behalf of any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred by, or +claims asserted against, such Contributor by reason of your accepting any such warranty or +additional liability. + +END OF TERMS AND CONDITIONS diff --git a/.gems/gems/thread_safe-0.3.4/README.md b/.gems/gems/thread_safe-0.3.4/README.md new file mode 100644 index 0000000..3f964f5 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/README.md @@ -0,0 +1,56 @@ +# Threadsafe + +[![Build Status](https://travis-ci.org/headius/thread_safe.png)](https://travis-ci.org/headius/thread_safe) + +A collection of thread-safe versions of common core Ruby classes. + +## Installation + +Add this line to your application's Gemfile: + + gem 'thread_safe' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install thread_safe + +## Usage + +```ruby +require 'thread_safe' + +sa = ThreadSafe::Array.new # supports standard Array.new forms +sh = ThreadSafe::Hash.new # supports standard Hash.new forms +``` + +`ThreadSafe::Cache` also exists, as a hash-like object, and should have +much better performance characteristics esp. under high concurrency than +`ThreadSafe::Hash`. However, `ThreadSafe::Cache` is not strictly semantically +equivalent to a ruby `Hash` -- for instance, it does not necessarily retain +ordering by insertion time as `Hash` does. For most uses it should do fine +though, and we recommend you consider `ThreadSafe::Cache` instead of +`ThreadSafe::Hash` for your concurrency-safe hash needs. It understands some +options when created (depending on your ruby platform) that control some of the +internals - when unsure just leave them out: + + +```ruby +require 'thread_safe' + +cache = ThreadSafe::Cache.new +``` + +## Contributing + +1. Fork it +2. Clone it (`git clone git@github.com:you/thread_safe.git`) +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Build the jar (`rake jar`) NOTE: Requires JRuby +5. Install dependencies (`bundle install`) +6. Commit your changes (`git commit -am 'Added some feature'`) +7. Push to the branch (`git push origin my-new-feature`) +8. Create new Pull Request diff --git a/.gems/gems/thread_safe-0.3.4/Rakefile b/.gems/gems/thread_safe-0.3.4/Rakefile new file mode 100644 index 0000000..8717d2f --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/Rakefile @@ -0,0 +1,48 @@ +require "bundler/gem_tasks" +require "rake/testtask" + +task :default => :test + +if defined?(JRUBY_VERSION) + require "ant" + + directory "pkg/classes" + directory 'pkg/tests' + + desc "Clean up build artifacts" + task :clean do + rm_rf "pkg/classes" + rm_rf "pkg/tests" + rm_rf "lib/thread_safe/jruby_cache_backend.jar" + end + + desc "Compile the extension" + task :compile => "pkg/classes" do |t| + ant.javac :srcdir => "ext", :destdir => t.prerequisites.first, + :source => "1.5", :target => "1.5", :debug => true, + :classpath => "${java.class.path}:${sun.boot.class.path}" + end + + desc "Build the jar" + task :jar => :compile do + ant.jar :basedir => "pkg/classes", :destfile => "lib/thread_safe/jruby_cache_backend.jar", :includes => "**/*.class" + end + + desc "Build test jar" + task 'test-jar' => 'pkg/tests' do |t| + ant.javac :srcdir => 'test/src', :destdir => t.prerequisites.first, + :source => "1.5", :target => "1.5", :debug => true + + ant.jar :basedir => 'pkg/tests', :destfile => 'test/package.jar', :includes => '**/*.class' + end + + task :package => [ :jar, 'test-jar' ] +else + # No need to package anything for non-jruby rubies + task :package +end + +Rake::TestTask.new :test => :package do |t| + t.libs << "lib" + t.test_files = FileList["test/**/*.rb"] +end diff --git a/.gems/gems/thread_safe-0.3.4/examples/bench_cache.rb b/.gems/gems/thread_safe-0.3.4/examples/bench_cache.rb new file mode 100755 index 0000000..14171c9 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/examples/bench_cache.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby -wKU + +require "benchmark" +require "thread_safe" + +hash = {} +cache = ThreadSafe::Cache.new + +ENTRIES = 10_000 + +ENTRIES.times do |i| + hash[i] = i + cache[i] = i +end + +TESTS = 40_000_000 +Benchmark.bmbm do |results| + key = rand(10_000) + + results.report('Hash#[]') do + TESTS.times { hash[key] } + end + + results.report('Cache#[]') do + TESTS.times { cache[key] } + end + + results.report('Hash#each_pair') do + (TESTS / ENTRIES).times { hash.each_pair {|k,v| v} } + end + + results.report('Cache#each_pair') do + (TESTS / ENTRIES).times { cache.each_pair {|k,v| v} } + end +end diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java new file mode 100644 index 0000000..8ff7b64 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java @@ -0,0 +1,245 @@ +package org.jruby.ext.thread_safe; + +import org.jruby.*; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMap; +import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMapV8; +import org.jruby.ext.thread_safe.jsr166e.nounsafe.*; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +import java.io.IOException; +import java.util.Map; + +import static org.jruby.runtime.Visibility.PRIVATE; + +/** + * Native Java implementation to avoid the JI overhead. + * + * @author thedarkone + */ +public class JRubyCacheBackendLibrary implements Library { + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyClass jrubyRefClass = runtime.defineClassUnder("JRubyCacheBackend", runtime.getObject(), BACKEND_ALLOCATOR, runtime.getModule("ThreadSafe")); + jrubyRefClass.setAllocator(BACKEND_ALLOCATOR); + jrubyRefClass.defineAnnotatedMethods(JRubyCacheBackend.class); + } + + private static final ObjectAllocator BACKEND_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyCacheBackend(runtime, klazz); + } + }; + + @JRubyClass(name="JRubyCacheBackend", parent="Object") + public static class JRubyCacheBackend extends RubyObject { + // Defaults used by the CHM + static final int DEFAULT_INITIAL_CAPACITY = 16; + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + public static final boolean CAN_USE_UNSAFE_CHM = canUseUnsafeCHM(); + + private ConcurrentHashMap map; + + private static ConcurrentHashMap newCHM(int initialCapacity, float loadFactor) { + if (CAN_USE_UNSAFE_CHM) { + return new ConcurrentHashMapV8(initialCapacity, loadFactor); + } else { + return new org.jruby.ext.thread_safe.jsr166e.nounsafe.ConcurrentHashMapV8(initialCapacity, loadFactor); + } + } + + private static ConcurrentHashMap newCHM() { + return newCHM(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + private static boolean canUseUnsafeCHM() { + try { + new org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMapV8(); // force class load and initialization + return true; + } catch (Throwable t) { // ensuring we really do catch everything + // Doug's Unsafe setup errors always have this "Could not ini.." message + if (t.getMessage().contains("Could not initialize intrinsics") || isCausedBySecurityException(t)) { + return false; + } + throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t)); + } + } + + private static boolean isCausedBySecurityException(Throwable t) { + while (t != null) { + if (t instanceof SecurityException) { + return true; + } + t = t.getCause(); + } + return false; + } + + public JRubyCacheBackend(Ruby runtime, RubyClass klass) { + super(runtime, klass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + map = newCHM(); + return context.getRuntime().getNil(); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject options) { + map = toCHM(context, options); + return context.getRuntime().getNil(); + } + + private ConcurrentHashMap toCHM(ThreadContext context, IRubyObject options) { + Ruby runtime = context.getRuntime(); + if (!options.isNil() && options.respondsTo("[]")) { + IRubyObject rInitialCapacity = options.callMethod(context, "[]", runtime.newSymbol("initial_capacity")); + IRubyObject rLoadFactor = options.callMethod(context, "[]", runtime.newSymbol("load_factor")); + int initialCapacity = !rInitialCapacity.isNil() ? RubyNumeric.num2int(rInitialCapacity.convertToInteger()) : DEFAULT_INITIAL_CAPACITY; + float loadFactor = !rLoadFactor.isNil() ? (float)RubyNumeric.num2dbl(rLoadFactor.convertToFloat()) : DEFAULT_LOAD_FACTOR; + return newCHM(initialCapacity, loadFactor); + } else { + return newCHM(); + } + } + + @JRubyMethod(name = "[]", required = 1) + public IRubyObject op_aref(ThreadContext context, IRubyObject key) { + IRubyObject value; + return ((value = map.get(key)) == null) ? context.getRuntime().getNil() : value; + } + + @JRubyMethod(name = {"[]="}, required = 2) + public IRubyObject op_aset(IRubyObject key, IRubyObject value) { + map.put(key, value); + return value; + } + + @JRubyMethod + public IRubyObject put_if_absent(IRubyObject key, IRubyObject value) { + IRubyObject result = map.putIfAbsent(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute_if_absent(final ThreadContext context, final IRubyObject key, final Block block) { + return map.computeIfAbsent(key, new ConcurrentHashMap.Fun() { + @Override + public IRubyObject apply(IRubyObject key) { + return block.yieldSpecific(context); + } + }); + } + + @JRubyMethod + public IRubyObject compute_if_present(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.computeIfPresent(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.compute(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject merge_pair(final ThreadContext context, final IRubyObject key, final IRubyObject value, final Block block) { + IRubyObject result = map.merge(key, value, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject oldValue, IRubyObject newValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObject newValue) { + return getRuntime().newBoolean(map.replace(key, oldValue, newValue)); + } + + @JRubyMethod(name = "key?", required = 1) + public RubyBoolean has_key_p(IRubyObject key) { + return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse(); + } + + @JRubyMethod + public IRubyObject key(IRubyObject value) { + final IRubyObject key = map.findKey(value); + return key == null ? getRuntime().getNil() : key; + } + + @JRubyMethod + public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) { + IRubyObject result = map.replace(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject get_and_set(IRubyObject key, IRubyObject value) { + IRubyObject result = map.put(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject delete(IRubyObject key) { + IRubyObject result = map.remove(key); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean delete_pair(IRubyObject key, IRubyObject value) { + return getRuntime().newBoolean(map.remove(key, value)); + } + + @JRubyMethod + public IRubyObject clear() { + map.clear(); + return this; + } + + @JRubyMethod + public IRubyObject each_pair(ThreadContext context, Block block) { + for (Map.Entry entry : map.entrySet()) { + block.yieldSpecific(context, entry.getKey(), entry.getValue()); + } + return this; + } + + @JRubyMethod + public RubyFixnum size(ThreadContext context) { + return context.getRuntime().newFixnum(map.size()); + } + + @JRubyMethod + public IRubyObject get_or_default(IRubyObject key, IRubyObject defaultValue) { + return map.getValueOrDefault(key, defaultValue); + } + + @JRubyMethod(visibility = PRIVATE) + public JRubyCacheBackend initialize_copy(ThreadContext context, IRubyObject other) { + map = newCHM(); + return this; + } + } +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java new file mode 100644 index 0000000..90ae1a2 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java @@ -0,0 +1,31 @@ +package org.jruby.ext.thread_safe.jsr166e; + +import java.util.Map; +import java.util.Set; + +public interface ConcurrentHashMap { + /** Interface describing a function of one argument */ + public interface Fun { T apply(A a); } + /** Interface describing a function of two arguments */ + public interface BiFun { T apply(A a, B b); } + + public V get(K key); + public V put(K key, V value); + public V putIfAbsent(K key, V value); + public V computeIfAbsent(K key, Fun mf); + public V computeIfPresent(K key, BiFun mf); + public V compute(K key, BiFun mf); + public V merge(K key, V value, BiFun mf); + public boolean replace(K key, V oldVal, V newVal); + public V replace(K key, V value); + public boolean containsKey(K key); + public boolean remove(Object key, Object value); + public V remove(K key); + public void clear(); + public Set> entrySet(); + public int size(); + public V getValueOrDefault(Object key, V defaultValue); + + public boolean containsValue(V value); + public K findKey(V value); +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java new file mode 100644 index 0000000..c85b989 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java @@ -0,0 +1,3863 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on the 1.79 version. + +package org.jruby.ext.thread_safe.jsr166e; + +import org.jruby.RubyClass; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.exceptions.RaiseException; +import org.jruby.ext.thread_safe.jsr166y.ThreadLocalRandom; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Enumeration; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import java.io.Serializable; + +/** + * A hash table supporting full concurrency of retrievals and + * high expected concurrency for updates. This class obeys the + * same functional specification as {@link java.util.Hashtable}, and + * includes versions of methods corresponding to each method of + * {@code Hashtable}. However, even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with {@code Hashtable} in programs that rely on its + * thread safety but not on its synchronization details. + * + *

Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently completed update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * happens-before relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do not throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. + * + *

The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. + * + *

A {@link Set} projection of a ConcurrentHashMapV8 may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + *

A ConcurrentHashMapV8 can be used as scalable frequency map (a + * form of histogram or multiset) by using {@link LongAdder} values + * and initializing via {@link #computeIfAbsent}. For example, to add + * a count to a {@code ConcurrentHashMapV8 freqs}, you + * can use {@code freqs.computeIfAbsent(k -> new + * LongAdder()).increment();} + * + *

This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} + * interfaces. + * + *

Like {@link Hashtable} but unlike {@link HashMap}, this class + * does not allow {@code null} to be used as a key or value. + * + *

ConcurrentHashMapV8s support parallel operations using the {@link + * ForkJoinPool#commonPool}. (Tasks that may be used in other contexts + * are available in class {@link ForkJoinTasks}). These operations are + * designed to be safely, and often sensibly, applied even with maps + * that are being concurrently updated by other threads; for example, + * when computing a snapshot summary of the values in a shared + * registry. There are three kinds of operation, each with four + * forms, accepting functions with Keys, Values, Entries, and (Key, + * Value) arguments and/or return values. (The first three forms are + * also available via the {@link #keySet()}, {@link #values()} and + * {@link #entrySet()} views). Because the elements of a + * ConcurrentHashMapV8 are not ordered in any particular way, and may be + * processed in different orders in different parallel executions, the + * correctness of supplied functions should not depend on any + * ordering, or on any other objects or values that may transiently + * change while computation is in progress; and except for forEach + * actions, should ideally be side-effect-free. + * + *

    + *
  • forEach: Perform a given action on each element. + * A variant form applies a given transformation on each element + * before performing the action.
  • + * + *
  • search: Return the first available non-null result of + * applying a given function on each element; skipping further + * search when a result is found.
  • + * + *
  • reduce: Accumulate each element. The supplied reduction + * function cannot rely on ordering (more formally, it should be + * both associative and commutative). There are five variants: + * + *
      + * + *
    • Plain reductions. (There is not a form of this method for + * (key, value) function arguments since there is no corresponding + * return type.)
    • + * + *
    • Mapped reductions that accumulate the results of a given + * function applied to each element.
    • + * + *
    • Reductions to scalar doubles, longs, and ints, using a + * given basis value.
    • + * + * + *
    + *
+ * + *

The concurrency properties of bulk operations follow + * from those of ConcurrentHashMapV8: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + *

Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + *

Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + *

Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + *

Parallel speedups for bulk operations compared to sequential + * processing are common but not guaranteed. Operations involving + * brief functions on small maps may execute more slowly than + * sequential loops if the underlying work to parallelize the + * computation is more expensive than the computation itself. + * Similarly, parallelization may not lead to much actual parallelism + * if all processors are busy performing unrelated tasks. + * + *

All arguments to all task methods must be non-null. + * + *

jsr166e note: During transition, this class + * uses nested functional interfaces with different names but the + * same forms as those expected for JDK8. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMapV8 + implements ConcurrentMap, Serializable, ConcurrentHashMap { + private static final long serialVersionUID = 7249069246763182397L; + + /** + * A partitionable iterator. A Spliterator can be traversed + * directly, but can also be partitioned (before traversal) by + * creating another Spliterator that covers a non-overlapping + * portion of the elements, and so may be amenable to parallel + * execution. + * + *

This interface exports a subset of expected JDK8 + * functionality. + * + *

Sample usage: Here is one (of the several) ways to compute + * the sum of the values held in a map using the ForkJoin + * framework. As illustrated here, Spliterators are well suited to + * designs in which a task repeatedly splits off half its work + * into forked subtasks until small enough to process directly, + * and then joins these subtasks. Variants of this style can also + * be used in completion-based designs. + * + *

+     * {@code ConcurrentHashMapV8 m = ...
+     * // split as if have 8 * parallelism, for load balance
+     * int n = m.size();
+     * int p = aForkJoinPool.getParallelism() * 8;
+     * int split = (n < p)? n : p;
+     * long sum = aForkJoinPool.invoke(new SumValues(m.valueSpliterator(), split, null));
+     * // ...
+     * static class SumValues extends RecursiveTask {
+     *   final Spliterator s;
+     *   final int split;             // split while > 1
+     *   final SumValues nextJoin;    // records forked subtasks to join
+     *   SumValues(Spliterator s, int depth, SumValues nextJoin) {
+     *     this.s = s; this.depth = depth; this.nextJoin = nextJoin;
+     *   }
+     *   public Long compute() {
+     *     long sum = 0;
+     *     SumValues subtasks = null; // fork subtasks
+     *     for (int s = split >>> 1; s > 0; s >>>= 1)
+     *       (subtasks = new SumValues(s.split(), s, subtasks)).fork();
+     *     while (s.hasNext())        // directly process remaining elements
+     *       sum += s.next();
+     *     for (SumValues t = subtasks; t != null; t = t.nextJoin)
+     *       sum += t.join();         // collect subtask results
+     *     return sum;
+     *   }
+     * }
+     * }
+ */ + public static interface Spliterator extends Iterator { + /** + * Returns a Spliterator covering approximately half of the + * elements, guaranteed not to overlap with those subsequently + * returned by this Spliterator. After invoking this method, + * the current Spliterator will not produce any of + * the elements of the returned Spliterator, but the two + * Spliterators together will produce all of the elements that + * would have been produced by this Spliterator had this + * method not been called. The exact number of elements + * produced by the returned Spliterator is not guaranteed, and + * may be zero (i.e., with {@code hasNext()} reporting {@code + * false}) if this Spliterator cannot be further split. + * + * @return a Spliterator covering approximately half of the + * elements + * @throws IllegalStateException if this Spliterator has + * already commenced traversing elements + */ + Spliterator split(); + } + + + /* + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * Each key-value mapping is held in a Node. Because Node fields + * can contain special values, they are defined using plain Object + * types. Similarly in turn, all internal methods that use them + * work off Object types. And similarly, so do the internal + * methods of auxiliary iterator and view classes. All public + * generic typed methods relay in/out of these internal methods, + * supplying null-checks and casts as needed. This also allows + * many of the public methods to be factored into a smaller number + * of internal methods (although sadly not so for the five + * variants of put-related operations). The validation-based + * approach explained below leads to a lot of code sprawl because + * retry-control precludes factoring into smaller methods. + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. The lists of nodes within bins + * are always accurately traversable under volatile reads, so long + * as lookups check hash code and non-nullness of value before + * checking key equality. + * + * We use the top two bits of Node hash fields for control + * purposes -- they are available anyway because of addressing + * constraints. As explained further below, these top bits are + * used as follows: + * 00 - Normal + * 01 - Locked + * 11 - Locked and may have a thread waiting for lock + * 10 - Node is a forwarding node + * + * The lower 30 bits of each Node's hash field contain a + * transformation of the key's hash code, except for forwarding + * nodes, for which the lower bits are zero (and so always have + * hash field == MOVED). + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Blocking support for these locks relies on the builtin + * "synchronized" monitors. However, we also need a tryLock + * construction, so we overlay these by using bits of the Node + * hash field for lock control (see above), and so normally use + * builtin monitors only for blocking and signalling using + * wait/notifyAll constructions. See Node.tryAwaitLock. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). However, + * operations that only conditionally update may inspect nodes + * until the point of update. This is a converse of sorts to the + * lazy locking technique described by Herlihy & Shavit. + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes. Also, although we guard + * against the worst effects of this (see method spread), sets of + * hashes may differ only in bits that do not impact their bin + * index for a given power-of-two mask. So we use a secondary + * strategy that applies when the number of nodes in a bin exceeds + * a threshold, and at least one of the keys implements + * Comparable. These TreeBins use a balanced tree to hold nodes + * (a specialized form of red-black trees), bounding search time + * to O(log N). Each search step in a TreeBin is around twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Only a single + * thread performs the resize (using field "sizeCtl", to arrange + * exclusion), but the table otherwise remains usable for reads + * and updates. Resizing proceeds by transferring bins, one by + * one, from the table to the next table. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. On + * average, only about one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage collectable as + * soon as they are no longer referenced by any reader thread that + * may be in the midst of concurrently traversing table. Upon + * transfer, the old table bin contains only a special forwarding + * node (with hash field "MOVED") that contains the next table as + * its key. On encountering a forwarding node, access and update + * operations restart, using the new table. + * + * Each bin transfer requires its bin lock. However, unlike other + * cases, a transfer can skip a bin if it fails to acquire its + * lock, and revisit it later (unless it is a TreeBin). Method + * rebuild maintains a buffer of TRANSFER_BUFFER_SIZE bins that + * have been skipped because of failure to acquire a lock, and + * blocks only if none are available (i.e., only very rarely). + * The transfer operation must also ensure that all accessible + * bins in both the old and new table are usable by any traversal. + * When there are no lock acquisition failures, this is arranged + * simply by proceeding from the last bin (table.length - 1) up + * towards the first. Upon seeing a forwarding node, traversals + * (see class Iter) arrange to move to the new table + * without revisiting nodes. However, when any node is skipped + * during a transfer, all earlier table bins may have become + * visible, so are initialized with a reverse-forwarding node back + * to the old table until the new ones are established. (This + * sometimes requires transiently locking a forwarding node, which + * is possible under the above encoding.) These more expensive + * mechanics trigger only when necessary. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a LongAdder, which avoids + * contention on updates but can encounter cache thrashing if read + * too frequently during concurrent access. To avoid reading so + * often, resizing is attempted either when a bin lock is + * contended, or upon adding to a bin already holding two or more + * nodes (checked before adding in the xIfAbsent methods, after + * adding in others). Under uniform hash distributions, the + * probability of this occurring at threshold is around 13%, + * meaning that only about 1 in 8 puts check threshold (and after + * resizing, many fewer do so). But this approximation has high + * variance for small table sizes, so we check on any collision + * for sizes <= 64. The bulk putAll operation further reduces + * contention by only committing count updates upon these size + * checks. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments refering to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + */ + + /* ---------------- Constants -------------- */ + + /** + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. + */ + private static final int DEFAULT_CAPACITY = 16; + + /** + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. + */ + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. + */ + private static final float LOAD_FACTOR = 0.75f; + + /** + * The buffer size for skipped bins during transfers. The + * value is arbitrary but should be large enough to avoid + * most locking stalls during resizes. + */ + private static final int TRANSFER_BUFFER_SIZE = 32; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. The value reflects the approximate break-even point for + * using tree-based operations. + * Note that Doug's version defaults to 8, but when dealing with + * Ruby objects it is actually beneficial to avoid TreeNodes + * as long as possible as it usually means going into Ruby land. + */ + private static final int TREE_THRESHOLD = 16; + + /* + * Encodings for special uses of Node hash fields. See above for + * explanation. + */ + static final int MOVED = 0x80000000; // hash field for forwarding nodes + static final int LOCKED = 0x40000000; // set/tested only as a bit + static final int WAITING = 0xc0000000; // both bits set/tested together + static final int HASH_BITS = 0x3fffffff; // usable bits of normal node hash + + /* ---------------- Fields -------------- */ + + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile Node[] table; + + /** + * The counter maintaining number of elements. + */ + private transient final LongAdder counter; + + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized. Otherwise, when table is + * null, holds the initial table size to use upon creation, or 0 + * for default. After initialization, holds the next element count + * value upon which to resize the table. + */ + private transient volatile int sizeCtl; + + // views + private transient KeySetView keySet; + private transient ValuesView values; + private transient EntrySetView entrySet; + + /** For serialization compatibility. Null unless serialized; see below */ + private Segment[] segments; + + /* ---------------- Table element access -------------- */ + + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. Uses are + * null checked by callers, and implicitly bounds-checked, relying + * on the invariants that tab arrays have non-zero size, and all + * indices are masked with (tab.length - 1) which is never + * negative and always less than length. Note that, to be correct + * wrt arbitrary concurrency errors by users, bounds checks must + * operate on local variables, which accounts for some odd-looking + * inline assignments below. + */ + + static final Node tabAt(Node[] tab, int i) { // used by Iter + return (Node)UNSAFE.getObjectVolatile(tab, ((long)i< 1 ? 64 : 1; + + /** + * Spins a while if LOCKED bit set and this node is the first + * of its bin, and then sets WAITING bits on hash field and + * blocks (once) if they are still set. It is OK for this + * method to return even if lock is not available upon exit, + * which enables these simple single-wait mechanics. + * + * The corresponding signalling operation is performed within + * callers: Upon detecting that WAITING has been set when + * unlocking lock (via a failed CAS from non-waiting LOCKED + * state), unlockers acquire the sync lock and perform a + * notifyAll. + * + * The initial sanity check on tab and bounds is not currently + * necessary in the only usages of this method, but enables + * use in other future contexts. + */ + final void tryAwaitLock(Node[] tab, int i) { + if (tab != null && i >= 0 && i < tab.length) { // sanity check + int r = ThreadLocalRandom.current().nextInt(); // randomize spins + int spins = MAX_SPINS, h; + while (tabAt(tab, i) == this && ((h = hash) & LOCKED) != 0) { + if (spins >= 0) { + r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift + if (r >= 0 && --spins == 0) + Thread.yield(); // yield before block + } + else if (casHash(h, h | WAITING)) { + synchronized (this) { + if (tabAt(tab, i) == this && + (hash & WAITING) == WAITING) { + try { + wait(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + else + notifyAll(); // possibly won race vs signaller + } + break; + } + } + } + } + + // Unsafe mechanics for casHash + private static final sun.misc.Unsafe UNSAFE; + private static final long hashOffset; + + static { + try { + UNSAFE = getUnsafe(); + Class k = Node.class; + hashOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("hash")); + } catch (Exception e) { + throw new Error(e); + } + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode extends Node { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, Object key, Object val, Node next, TreeNode parent) { + super(hash, key, val, next); + this.parent = parent; + } + } + + /** + * A specialized form of red-black tree for use in bins + * whose size exceeds a threshold. + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable + * for the same T, so we cannot invoke compareTo among them. To + * handle this, the tree is ordered primarily by hash value, then + * by getClass().getName() order, and then by Comparator order + * among elements of the same class. On lookup at a node, if + * elements are not comparable or compare as 0, both left and + * right children may need to be searched in the case of tied hash + * values. (This corresponds to the full list search that would be + * necessary if all elements were non-Comparable and had tied + * hashes.) The red-black balancing code is updated from + * pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also maintain a separate locking discipline than + * regular bins. Because they are forwarded via special MOVED + * nodes at bin heads (which can never change once established), + * we cannot use those nodes as locks. Instead, TreeBin + * extends AbstractQueuedSynchronizer to support a simple form of + * read-write lock. For update operations and table validation, + * the exclusive form of lock behaves in the same way as bin-head + * locks. However, lookups use shared read-lock mechanics to allow + * multiple readers in the absence of writers. Additionally, + * these lookups do not ever block: While the lock is not + * available, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. (These cases are not fast, + * but maximize aggregate expected throughput.) The AQS mechanics + * for doing this are straightforward. The lock state is held as + * AQS getState(). Read counts are negative; the write count (1) + * is positive. There are no signalling preferences among readers + * and writers. Since we don't need to export full Lock API, we + * just override the minimal AQS methods and use them directly. + */ + static final class TreeBin extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 2249069246763182397L; + transient TreeNode root; // root of tree + transient TreeNode first; // head of next-pointer list + + /* AQS overrides */ + public final boolean isHeldExclusively() { return getState() > 0; } + public final boolean tryAcquire(int ignore) { + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + public final boolean tryRelease(int ignore) { + setExclusiveOwnerThread(null); + setState(0); + return true; + } + public final int tryAcquireShared(int ignore) { + for (int c;;) { + if ((c = getState()) > 0) + return -1; + if (compareAndSetState(c, c -1)) + return 1; + } + } + public final boolean tryReleaseShared(int ignore) { + int c; + do {} while (!compareAndSetState(c = getState(), c + 1)); + return c == -1; + } + + /** From CLR */ + private void rotateLeft(TreeNode p) { + if (p != null) { + TreeNode r = p.right, pp, rl; + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + root = r; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + } + + /** From CLR */ + private void rotateRight(TreeNode p) { + if (p != null) { + TreeNode l = p.left, pp, lr; + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + root = l; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + } + + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, Object k, TreeNode p) { + return getTreeNode(h, (RubyObject)k, p); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, RubyObject k, TreeNode p) { + RubyClass c = k.getMetaClass(); boolean kNotComparable = !k.respondsTo("<=>"); + while (p != null) { + int dir, ph; RubyObject pk; RubyClass pc; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = (RubyClass)pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pl, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + // try to continue iterating on the left side + else if ((pl = p.left) != null && h <= pl.hash) + dir = -1; + else // no matching node found + return null; + } + } + } + else + dir = (h < ph) ? -1 : 1; + p = (dir > 0) ? p.right : p.left; + } + return null; + } + + int rubyCompare(RubyObject l, RubyObject r) { + ThreadContext context = l.getMetaClass().getRuntime().getCurrentContext(); + IRubyObject result; + try { + result = l.callMethod(context, "<=>", r); + } catch (RaiseException e) { + // handle objects "lying" about responding to <=>, ie: an Array containing non-comparable keys + if (context.runtime.getNoMethodError().isInstance(e.getException())) { + return 0; + } + throw e; + } + + return result.isNil() ? 0 : RubyNumeric.num2int(result.convertToInteger()); + } + + /** + * Wrapper for getTreeNode used by CHM.get. Tries to obtain + * read-lock to call getTreeNode, but during failure to get + * lock, searches along next links. + */ + final Object getValue(int h, Object k) { + Node r = null; + int c = getState(); // Must read lock state first + for (Node e = first; e != null; e = e.next) { + if (c <= 0 && compareAndSetState(c, c - 1)) { + try { + r = getTreeNode(h, k, root); + } finally { + releaseShared(0); + } + break; + } + else if ((e.hash & HASH_BITS) == h && k.equals(e.key)) { + r = e; + break; + } + else + c = getState(); + } + return r == null ? null : r.val; + } + + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, Object k, Object v) { + return putTreeNode(h, (RubyObject)k, v); + } + + /** + * Finds or adds a node. + * @return null if added + */ + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, RubyObject k, Object v) { + RubyClass c = k.getMetaClass(); + boolean kNotComparable = !k.respondsTo("<=>"); + TreeNode pp = root, p = null; + int dir = 0; + while (pp != null) { // find existing node or leaf to insert at + int ph; RubyObject pk; RubyClass pc; + p = pp; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + else // continue descending down the left subtree + dir = -1; + } + } + } + else + dir = (h < ph) ? -1 : 1; + pp = (dir > 0) ? p.right : p.left; + } + + TreeNode f = first; + TreeNode x = first = new TreeNode(h, (Object)k, v, f, p); + if (p == null) + root = x; + else { // attach and rebalance; adapted from CLR + TreeNode xp, xpp; + if (f != null) + f.prev = x; + if (dir <= 0) + p.left = x; + else + p.right = x; + x.red = true; + while (x != null && (xp = x.parent) != null && xp.red && + (xpp = xp.parent) != null) { + TreeNode xppl = xpp.left; + if (xp == xppl) { + TreeNode y = xpp.right; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + rotateLeft(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateRight(xpp); + } + } + } + } + else { + TreeNode y = xppl; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + rotateRight(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateLeft(xpp); + } + } + } + } + } + TreeNode r = root; + if (r != null && r.red) + r.red = false; + } + return null; + } + + /** + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + */ + final void deleteTreeNode(TreeNode p) { + TreeNode next = (TreeNode)p.next; // unlink traversal pointers + TreeNode pred = p.prev; + if (pred == null) + first = next; + else + pred.next = next; + if (next != null) + next.prev = pred; + TreeNode replacement; + TreeNode pl = p.left; + TreeNode pr = p.right; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + replacement = sr; + } + else + replacement = (pl != null) ? pl : pr; + TreeNode pp = p.parent; + if (replacement == null) { + if (pp == null) { + root = null; + return; + } + replacement = p; + } + else { + replacement.parent = pp; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + if (!p.red) { // rebalance, from CLR + TreeNode x = replacement; + while (x != null) { + TreeNode xp, xpl; + if (x.red || (xp = x.parent) == null) { + x.red = false; + break; + } + if (x == (xpl = xp.left)) { + TreeNode sib = xp.right; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateLeft(xp); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + sib.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + sib.red = true; + rotateRight(sib); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sr = sib.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + rotateLeft(xp); + } + x = root; + } + } + } + else { // symmetric + TreeNode sib = xpl; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateRight(xp); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + sib.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + sib.red = true; + rotateLeft(sib); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sl = sib.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + rotateRight(xp); + } + x = root; + } + } + } + } + } + if (p == replacement && (pp = p.parent) != null) { + if (p == pp.left) // detach pointers + pp.left = null; + else if (p == pp.right) + pp.right = null; + p.parent = null; + } + } + } + + /* ---------------- Collision reduction methods -------------- */ + + /** + * Spreads higher bits to lower, and also forces top 2 bits to 0. + * Because the table uses power-of-two masking, sets of hashes + * that vary only in bits above the current mask will always + * collide. (Among known examples are sets of Float keys holding + * consecutive whole numbers in small tables.) To counter this, + * we apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed across bits (so don't benefit + * from spreading), and because we use trees to handle large sets + * of collisions in bins, we don't need excessively high quality. + */ + private static final int spread(int h) { + h ^= (h >>> 18) ^ (h >>> 12); + return (h ^ (h >>> 10)) & HASH_BITS; + } + + /** + * Replaces a list bin with a tree bin. Call only when locked. + * Fails to replace if the given key is non-comparable or table + * is, or needs, resizing. + */ + private final void replaceWithTreeBin(Node[] tab, int index, Object key) { + if ((key instanceof Comparable) && + (tab.length >= MAXIMUM_CAPACITY || counter.sum() < (long)sizeCtl)) { + TreeBin t = new TreeBin(); + for (Node e = tabAt(tab, index); e != null; e = e.next) + t.putTreeNode(e.hash & HASH_BITS, e.key, e.val); + setTabAt(tab, index, new Node(MOVED, t, null, null)); + } + } + + /* ---------------- Internal access and update methods -------------- */ + + /** Implementation for get and containsKey */ + private final Object internalGet(Object k) { + int h = spread(k.hashCode()); + retry: for (Node[] tab = table; tab != null;) { + Node e, p; Object ek, ev; int eh; // locals to read fields once + for (e = tabAt(tab, (tab.length - 1) & h); e != null; e = e.next) { + if ((eh = e.hash) == MOVED) { + if ((ek = e.key) instanceof TreeBin) // search TreeBin + return ((TreeBin)ek).getValue(h, k); + else { // restart with new table + tab = (Node[])ek; + continue retry; + } + } + else if ((eh & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + } + break; + } + return null; + } + + /** + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. + */ + private final Object internalReplace(Object k, Object v, Object cv) { + int h = spread(k.hashCode()); + Object oldVal = null; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null || + (f = tabAt(tab, i = (tab.length - 1) & h)) == null) + break; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + boolean deleted = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) { + Object pv = p.val; + if (cv == null || cv == pv || cv.equals(pv)) { + oldVal = pv; + if ((p.val = v) == null) { + deleted = true; + t.deleteTreeNode(p); + } + } + } + } + } finally { + t.release(0); + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) != h && f.next == null) // precheck + break; // rules out possible existence + else if ((fh & LOCKED) != 0) { + checkForResize(); // try resizing if can't get lock + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + boolean validated = false; + boolean deleted = false; + try { + if (tabAt(tab, i) == f) { + validated = true; + for (Node e = f, pred = null;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + ((ev = e.val) != null) && + ((ek = e.key) == k || k.equals(ek))) { + if (cv == null || cv == ev || cv.equals(ev)) { + oldVal = ev; + if ((e.val = v) == null) { + deleted = true; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + } + return oldVal; + } + + /* + * Internal versions of the six insertion methods, each a + * little more complicated than the last. All have + * the same basic structure as the first (internalPut): + * 1. If table uninitialized, create + * 2. If bin empty, try to CAS new node + * 3. If bin stale, use new table + * 4. if bin converted to TreeBin, validate and relay to TreeBin methods + * 5. Lock and validate; if valid, scan and add or update + * + * The others interweave other checks and/or alternative actions: + * * Plain put checks for and performs resize after insertion. + * * putIfAbsent prescans for mapping without lock (and fails to add + * if present), which also makes pre-emptive resize checks worthwhile. + * * computeIfAbsent extends form used in putIfAbsent with additional + * mechanics to deal with, calls, potential exceptions and null + * returns from function call. + * * compute uses the same function-call mechanics, but without + * the prescans + * * merge acts as putIfAbsent in the absent case, but invokes the + * update function if present + * * putAll attempts to pre-allocate enough table space + * and more lazily performs count updates and checks. + * + * Someday when details settle down a bit more, it might be worth + * some factoring to reduce sprawl. + */ + + /** Implementation for put */ + private final Object internalPut(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) { + oldVal = p.val; + p.val = v; + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { // needed in case equals() throws + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { // unlock and signal if needed + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length <= 64) + count = 2; + break; + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for putIfAbsent */ + private final Object internalPutIfAbsent(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) + oldVal = p.val; + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { // at least 2 nodes -- search and maybe resize + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length <= 64) + count = 2; + break; + } + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for computeIfAbsent */ + private final Object internalComputeIfAbsent(K k, + Fun mf) { + int h = spread(k.hashCode()); + Object val = null; + int count = 0; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + count = 1; + try { + if ((val = mf.apply(k)) != null) + node.val = val; + } finally { + if (val == null) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean added = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + val = p.val; + else if ((val = mf.apply(k)) != null) { + added = true; + count = 2; + t.putTreeNode(h, k, val); + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (!added) + return val; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + boolean added = false; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + if ((val = mf.apply(k)) != null) { + added = true; + last.next = new Node(h, k, val, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (!added) + return val; + if (tab.length <= 64) + count = 2; + break; + } + } + } + } + if (val != null) { + counter.add(1L); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for compute */ + @SuppressWarnings("unchecked") private final Object internalCompute + (K k, boolean onlyIfPresent, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (onlyIfPresent) + break; + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + try { + count = 1; + if ((val = mf.apply(k, null)) != null) { + node.val = val; + delta = 1; + } + } finally { + if (delta == 0) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + Object pv; + if (p == null) { + if (onlyIfPresent) + break; + pv = null; + } else + pv = p.val; + if ((val = mf.apply(k, (V)pv)) != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply(k, (V)ev); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + if (!onlyIfPresent && (val = mf.apply(k, null)) != null) { + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for merge */ + @SuppressWarnings("unchecked") private final Object internalMerge + (K k, V v, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + delta = 1; + val = v; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + val = (p == null) ? v : mf.apply((V)p.val, v); + if (val != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply((V)ev, v); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = v; + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for putAll */ + private final void internalPutAll(Map m) { + tryPresize(m.size()); + long delta = 0L; // number of uncommitted additions + boolean npe = false; // to throw exception on exit for nulls + try { // to clean up counts on other exceptions + for (Map.Entry entry : m.entrySet()) { + Object k, v; + if (entry == null || (k = entry.getKey()) == null || + (v = entry.getValue()) == null) { + npe = true; + break; + } + int h = spread(k.hashCode()); + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null){ + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + ++delta; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + p.val = v; + else { + t.putTreeNode(h, k, v); + ++delta; + } + } + } finally { + t.release(0); + } + if (validated) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); + delta = 0L; + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + int count = 0; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + ++delta; + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (count > 1) { + counter.add(delta); + delta = 0L; + checkForResize(); + } + break; + } + } + } + } + } finally { + if (delta != 0) + counter.add(delta); + } + if (npe) + throw new NullPointerException(); + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 + */ + private static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private final Node[] initTable() { + Node[] tab; int sc; + while ((tab = table) == null) { + if ((sc = sizeCtl) < 0) + Thread.yield(); // lost initialization race; just spin + else if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if ((tab = table) == null) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + tab = table = new Node[n]; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; + } + + /** + * If table is too small and not already resizing, creates next + * table and transfers bins. Rechecks occupancy after a transfer + * to see if another resize is already needed because resizings + * are lagging additions. + */ + private final void checkForResize() { + Node[] tab; int n, sc; + while ((tab = table) != null && + (n = tab.length) < MAXIMUM_CAPACITY && + (sc = sizeCtl) >= 0 && counter.sum() >= (long)sc && + UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (tab == table) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private final void tryPresize(int size) { + int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + Node[] tab = table; int n; + if (tab == null || (n = tab.length) == 0) { + n = (sc > c) ? sc : c; + if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == tab) { + table = new Node[n]; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) + break; + else if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == tab) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + } + + /* + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + * + * @return the new table + */ + private static final Node[] rebuild(Node[] tab) { + int n = tab.length; + Node[] nextTab = new Node[n << 1]; + Node fwd = new Node(MOVED, nextTab, null, null); + int[] buffer = null; // holds bins to revisit; null until needed + Node rev = null; // reverse forwarder; null until needed + int nbuffered = 0; // the number of bins in buffer list + int bufferIndex = 0; // buffer index of current buffered bin + int bin = n - 1; // current non-buffered bin or -1 if none + + for (int i = bin;;) { // start upwards sweep + int fh; Node f; + if ((f = tabAt(tab, i)) == null) { + if (bin >= 0) { // Unbuffered; no lock needed (or available) + if (!casTabAt(tab, i, f, fwd)) + continue; + } + else { // transiently use a locked forwarding node + Node g = new Node(MOVED|LOCKED, nextTab, null, null); + if (!casTabAt(tab, i, f, g)) + continue; + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + setTabAt(tab, i, fwd); + if (!g.casHash(MOVED|LOCKED, MOVED)) { + g.hash = MOVED; + synchronized (g) { g.notifyAll(); } + } + } + } + else if ((fh = f.hash) == MOVED) { + Object fk = f.key; + if (fk instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + splitTreeBin(nextTab, i, t); + setTabAt(tab, i, fwd); + } + } finally { + t.release(0); + } + if (!validated) + continue; + } + } + else if ((fh & LOCKED) == 0 && f.casHash(fh, fh|LOCKED)) { + boolean validated = false; + try { // split to lo and hi lists; copying as needed + if (tabAt(tab, i) == f) { + validated = true; + splitBin(nextTab, i, f); + setTabAt(tab, i, fwd); + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (!validated) + continue; + } + else { + if (buffer == null) // initialize buffer for revisits + buffer = new int[TRANSFER_BUFFER_SIZE]; + if (bin < 0 && bufferIndex > 0) { + int j = buffer[--bufferIndex]; + buffer[bufferIndex] = i; + i = j; // swap with another bin + continue; + } + if (bin < 0 || nbuffered >= TRANSFER_BUFFER_SIZE) { + f.tryAwaitLock(tab, i); + continue; // no other options -- block + } + if (rev == null) // initialize reverse-forwarder + rev = new Node(MOVED, tab, null, null); + if (tabAt(tab, i) != f || (f.hash & LOCKED) == 0) + continue; // recheck before adding to list + buffer[nbuffered++] = i; + setTabAt(nextTab, i, rev); // install place-holders + setTabAt(nextTab, i + n, rev); + } + + if (bin > 0) + i = --bin; + else if (buffer != null && nbuffered > 0) { + bin = -1; + i = buffer[bufferIndex = --nbuffered]; + } + else + return nextTab; + } + } + + /** + * Splits a normal bin with list headed by e into lo and hi parts; + * installs in given table. + */ + private static void splitBin(Node[] nextTab, int i, Node e) { + int bit = nextTab.length >>> 1; // bit to split on + int runBit = e.hash & bit; + Node lastRun = e, lo = null, hi = null; + for (Node p = e.next; p != null; p = p.next) { + int b = p.hash & bit; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) + lo = lastRun; + else + hi = lastRun; + for (Node p = e; p != lastRun; p = p.next) { + int ph = p.hash & HASH_BITS; + Object pk = p.key, pv = p.val; + if ((ph & bit) == 0) + lo = new Node(ph, pk, pv, lo); + else + hi = new Node(ph, pk, pv, hi); + } + setTabAt(nextTab, i, lo); + setTabAt(nextTab, i + bit, hi); + } + + /** + * Splits a tree bin into lo and hi parts; installs in given table. + */ + private static void splitTreeBin(Node[] nextTab, int i, TreeBin t) { + int bit = nextTab.length >>> 1; + TreeBin lt = new TreeBin(); + TreeBin ht = new TreeBin(); + int lc = 0, hc = 0; + for (Node e = t.first; e != null; e = e.next) { + int h = e.hash & HASH_BITS; + Object k = e.key, v = e.val; + if ((h & bit) == 0) { + ++lc; + lt.putTreeNode(h, k, v); + } + else { + ++hc; + ht.putTreeNode(h, k, v); + } + } + Node ln, hn; // throw away trees if too small + if (lc <= (TREE_THRESHOLD >>> 1)) { + ln = null; + for (Node p = lt.first; p != null; p = p.next) + ln = new Node(p.hash, p.key, p.val, ln); + } + else + ln = new Node(MOVED, lt, null, null); + setTabAt(nextTab, i, ln); + if (hc <= (TREE_THRESHOLD >>> 1)) { + hn = null; + for (Node p = ht.first; p != null; p = p.next) + hn = new Node(p.hash, p.key, p.val, hn); + } + else + hn = new Node(MOVED, ht, null, null); + setTabAt(nextTab, i + bit, hn); + } + + /** + * Implementation for clear. Steps through each bin, removing all + * nodes. + */ + private final void internalClear() { + long delta = 0L; // negative number of deletions + int i = 0; + Node[] tab = table; + while (tab != null && i < tab.length) { + int fh; Object fk; + Node f = tabAt(tab, i); + if (f == null) + ++i; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + for (Node p = t.first; p != null; p = p.next) { + if (p.val != null) { // (currently always true) + p.val = null; + --delta; + } + } + t.first = null; + t.root = null; + ++i; + } + } finally { + t.release(0); + } + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); // opportunistically update count + delta = 0L; + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + for (Node e = f; e != null; e = e.next) { + if (e.val != null) { // (currently always true) + e.val = null; + --delta; + } + } + setTabAt(tab, i, null); + ++i; + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + } + } + if (delta != 0) + counter.add(delta); + } + + /* ----------------Table Traversal -------------- */ + + /** + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators and bulk tasks. + * + * At each step, the iterator snapshots the key ("nextKey") and + * value ("nextVal") of a valid node (i.e., one that, at point of + * snapshot, has a non-null user value). Because val fields can + * change (including to null, indicating deletion), field nextVal + * might not be accurate at point of use, but still maintains the + * weak consistency property of holding a value that was once + * valid. To support iterator.remove, the nextKey field is not + * updated (nulled out) when the iterator cannot advance. + * + * Internal traversals directly access these fields, as in: + * {@code while (it.advance() != null) { process(it.nextKey); }} + * + * Exported iterators must track whether the iterator has advanced + * (in hasNext vs next) (by setting/checking/nulling field + * nextVal), and then extract key, value, or key-value pairs as + * return values of next(). + * + * The iterator visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. + * + * This class extends ForkJoinTask to streamline parallel + * iteration in bulk operations (see BulkTask). This adds only an + * int of space overhead, which is close enough to negligible in + * cases where it is not needed to not worry about it. Because + * ForkJoinTask is Serializable, but iterators need not be, we + * need to add warning suppressions. + */ + @SuppressWarnings("serial") static class Traverser { + final ConcurrentHashMapV8 map; + Node next; // the next entry to use + K nextKey; // cached key field of next + V nextVal; // cached val field of next + Node[] tab; // current table; updated if resized + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + int baseSize; // initial table size + + /** Creates iterator for all entries in the table. */ + Traverser(ConcurrentHashMapV8 map) { + this.map = map; + } + + /** Creates iterator for split() methods */ + Traverser(Traverser it) { + ConcurrentHashMapV8 m; Node[] t; + if ((m = this.map = it.map) == null) + t = null; + else if ((t = it.tab) == null && // force parent tab initialization + (t = it.tab = m.table) != null) + it.baseLimit = it.baseSize = t.length; + this.tab = t; + this.baseSize = it.baseSize; + it.baseLimit = this.index = this.baseIndex = + ((this.baseLimit = it.baseLimit) + it.baseIndex + 1) >>> 1; + } + + /** + * Advances next; returns nextVal or null if terminated. + * See above for explanation. + */ + final V advance() { + Node e = next; + V ev = null; + outer: do { + if (e != null) // advance past used/skipped node + e = e.next; + while (e == null) { // get to next non-null bin + ConcurrentHashMapV8 m; + Node[] t; int b, i, n; Object ek; // checks must use locals + if ((t = tab) != null) + n = t.length; + else if ((m = map) != null && (t = tab = m.table) != null) + n = baseLimit = baseSize = t.length; + else + break outer; + if ((b = baseIndex) >= baseLimit || + (i = index) < 0 || i >= n) + break outer; + if ((e = tabAt(t, i)) != null && e.hash == MOVED) { + if ((ek = e.key) instanceof TreeBin) + e = ((TreeBin)ek).first; + else { + tab = (Node[])ek; + continue; // restarts due to null val + } + } // visit upper slots if present + index = (i += baseSize) < n ? i : (baseIndex = b + 1); + } + nextKey = (K) e.key; + } while ((ev = (V) e.val) == null); // skip deleted or special nodes + next = e; + return nextVal = ev; + } + + public final void remove() { + Object k = nextKey; + if (k == null && (advance() == null || (k = nextKey) == null)) + throw new IllegalStateException(); + map.internalReplace(k, null, null); + } + + public final boolean hasNext() { + return nextVal != null || advance() != null; + } + + public final boolean hasMoreElements() { return hasNext(); } + public final void setRawResult(Object x) { } + public R getRawResult() { return null; } + public boolean exec() { return true; } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMapV8() { + this.counter = new LongAdder(); + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMapV8(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(); + int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMapV8(Map m) { + this.counter = new LongAdder(); + this.sizeCtl = DEFAULT_CAPACITY; + internalPutAll(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * + * @since 1.6 + */ + public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMapV8(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + if (initialCapacity < concurrencyLevel) // Use at least as many bins + initialCapacity = concurrencyLevel; // as estimated threads + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = (size >= (long)MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + */ + public static KeySetView newKeySet() { + return new KeySetView(new ConcurrentHashMapV8(), + Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @return the new set + */ + public static KeySetView newKeySet(int initialCapacity) { + return new KeySetView(new ConcurrentHashMapV8(initialCapacity), + Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return counter.sum() <= 0L; // ignore transient negative values + } + + /** + * {@inheritDoc} + */ + public int size() { + long n = counter.sum(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); + } + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMapV8 may + * contain more mappings than can be represented as an int. The + * value returned is a snapshot; the actual count may differ if + * there are ongoing concurrent insertions or removals. + * + * @return the number of mappings + */ + public long mappingCount() { + long n = counter.sum(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V get(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalGet(key); + } + + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V getValueOrDefault(Object key, V defaultValue) { + if (key == null) + throw new NullPointerException(); + V v = (V) internalGet(key); + return v == null ? defaultValue : v; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object + * is a key in this table, as determined by the + * {@code equals} method; {@code false} otherwise + * @throws NullPointerException if the specified key is null + */ + public boolean containsKey(Object key) { + if (key == null) + throw new NullPointerException(); + return internalGet(key) != null; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + * @throws NullPointerException if the specified value is null + */ + public boolean containsValue(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return true; + } + return false; + } + + public K findKey(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return it.nextKey; + } + return null; + } + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

The value can be retrieved by calling the {@code get} method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V put(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPut(key, value); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPutIfAbsent(key, value); + } + + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + internalPutAll(m); + } + + /** + * If the specified key is not already associated with a value, + * computes its value using the given mappingFunction and enters + * it into the map unless null. This is equivalent to + *

 {@code
+     * if (map.containsKey(key))
+     *   return map.get(key);
+     * value = mappingFunction.apply(key);
+     * if (value != null)
+     *   map.put(key, value);
+     * return value;}
+ * + * except that the action is performed atomically. If the + * function returns {@code null} no mapping is recorded. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and no mapping is recorded. Some + * attempted update operations on this map by other threads may be + * blocked while computation is in progress, so the computation + * should be short and simple, and must not attempt to update any + * other mappings of this Map. The most appropriate usage is to + * construct a new object serving as an initial mapped value, or + * memoized result, as in: + * + *
 {@code
+     * map.computeIfAbsent(key, new Fun() {
+     *   public V map(K k) { return new Value(f(k)); }});}
+ * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished + */ + @SuppressWarnings("unchecked") public V computeIfAbsent + (K key, Fun mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + return (V)internalComputeIfAbsent(key, mappingFunction); + } + + /** + * If the given key is present, computes a new mapping value given a key and + * its current mapped value. This is equivalent to + *
 {@code
+     *   if (map.containsKey(key)) {
+     *     value = remappingFunction.apply(key, map.get(key));
+     *     if (value != null)
+     *       map.put(key, value);
+     *     else
+     *       map.remove(key);
+     *   }
+     * }
+ * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V computeIfPresent + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, true, remappingFunction); + } + + /** + * Computes a new mapping value given a key and + * its current mapped value (or {@code null} if there is no current + * mapping). This is equivalent to + *
 {@code
+     *   value = remappingFunction.apply(key, map.get(key));
+     *   if (value != null)
+     *     map.put(key, value);
+     *   else
+     *     map.remove(key);
+     * }
+ * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + *
 {@code
+     * Map map = ...;
+     * final String msg = ...;
+     * map.compute(key, new BiFun() {
+     *   public String apply(Key k, String v) {
+     *    return (v == null) ? msg : v + msg;});}}
+ * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V compute + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, false, remappingFunction); + } + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * Otherwise, replace the value with the results of + * the given remapping function. This is equivalent to: + *
 {@code
+     *   if (!map.containsKey(key))
+     *     map.put(value);
+     *   else {
+     *     newValue = remappingFunction.apply(map.get(key), value);
+     *     if (value != null)
+     *       map.put(key, value);
+     *     else
+     *       map.remove(key);
+     *   }
+     * }
+ * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. + */ + @SuppressWarnings("unchecked") public V merge + (K key, V value, BiFun remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalMerge(key, value, remappingFunction); + } + + /** + * Removes the key (and its corresponding value) from this map. + * This method does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V remove(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalReplace(key, null, null); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return false; + return internalReplace(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + return internalReplace(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V replace(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalReplace(key, value, null); + } + + /** + * Removes all of the mappings from this map. + */ + public void clear() { + internalClear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. + * + * @return the set view + */ + public KeySetView keySet() { + KeySetView ks = keySet; + return (ks != null) ? ks : (keySet = new KeySetView(this, null)); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll}). This is of + * course only appropriate if it is acceptable to use the same + * value for all additions from this view. + * + * @param mappedValue the mapped value to use for any + * additions. + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView(this, mappedValue); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. + */ + public ValuesView values() { + ValuesView vs = values; + return (vs != null) ? vs : (values = new ValuesView(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public Set> entrySet() { + EntrySetView es = entrySet; + return (es != null) ? es : (entrySet = new EntrySetView(this)); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the keys in this map. + * + * @return a partitionable iterator of the keys in this map + */ + public Spliterator keySpliterator() { + return new KeyIterator(this); + } + + /** + * Returns a partitionable iterator of the values in this map. + * + * @return a partitionable iterator of the values in this map + */ + public Spliterator valueSpliterator() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the entries in this map. + * + * @return a partitionable iterator of the entries in this map + */ + public Spliterator> entrySpliterator() { + return new EntryIterator(this); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + h += it.nextKey.hashCode() ^ v.hashCode(); + } + return h; + } + + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Traverser it = new Traverser(this); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Object v; + if ((v = it.advance()) != null) { + for (;;) { + Object k = it.nextKey; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((v = it.advance()) == null) + break; + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } + + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + Traverser it = new Traverser(this); + Object val; + while ((val = it.advance()) != null) { + Object v = m.get(it.nextKey); + if (v == null || (v != val && !v.equals(val))) + return false; + } + for (Map.Entry e : m.entrySet()) { + Object mk, mv, v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = internalGet(mk)) == null || + (mv != v && !mv.equals(v))) + return false; + } + } + return true; + } + + /* ----------------Iterators -------------- */ + + @SuppressWarnings("serial") static final class KeyIterator extends Traverser + implements Spliterator, Enumeration { + KeyIterator(ConcurrentHashMapV8 map) { super(map); } + KeyIterator(Traverser it) { + super(it); + } + public KeyIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new KeyIterator(this); + } + @SuppressWarnings("unchecked") public final K next() { + if (nextVal == null && advance() == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return (K) k; + } + + public final K nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class ValueIterator extends Traverser + implements Spliterator, Enumeration { + ValueIterator(ConcurrentHashMapV8 map) { super(map); } + ValueIterator(Traverser it) { + super(it); + } + public ValueIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new ValueIterator(this); + } + + @SuppressWarnings("unchecked") public final V next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + nextVal = null; + return (V) v; + } + + public final V nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class EntryIterator extends Traverser + implements Spliterator> { + EntryIterator(ConcurrentHashMapV8 map) { super(map); } + EntryIterator(Traverser it) { + super(it); + } + public EntryIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new EntryIterator(this); + } + + @SuppressWarnings("unchecked") public final Map.Entry next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return new MapEntry((K)k, (V)v, map); + } + } + + /** + * Exported Entry for iterators + */ + static final class MapEntry implements Map.Entry { + final K key; // non-null + V val; // non-null + final ConcurrentHashMapV8 map; + MapEntry(K key, V val, ConcurrentHashMapV8 map) { + this.key = key; + this.val = val; + this.map = map; + } + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString(){ return key + "=" + val; } + + public final boolean equals(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == val || v.equals(val))); + } + + /** + * Sets our entry's value and writes through to the map. The + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed in which case the put will + * re-establish). We do not and cannot guarantee more. + */ + public final V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = val; + val = value; + map.put(key, value); + return v; + } + } + + /* ---------------- Serialization Support -------------- */ + + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + Segment(float lf) { this.loadFactor = lf; } + } + + /** + * Saves the state of the {@code ConcurrentHashMapV8} instance to a + * stream (i.e., serializes it). + * @param s the stream + * @serialData + * the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + @SuppressWarnings("unchecked") private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + if (segments == null) { // for serialization compatibility + segments = (Segment[]) + new Segment[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) + segments[i] = new Segment(LOAD_FACTOR); + } + s.defaultWriteObject(); + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + s.writeObject(it.nextKey); + s.writeObject(v); + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * @param s the stream + */ + @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + this.segments = null; // unneeded + // initialize transient final field + UNSAFE.putObjectVolatile(this, counterOffset, new LongAdder()); + + // Create all nodes, then place in table once size is known + long size = 0L; + Node p = null; + for (;;) { + K k = (K) s.readObject(); + V v = (V) s.readObject(); + if (k != null && v != null) { + int h = spread(k.hashCode()); + p = new Node(h, k, v, p); + ++size; + } + else + break; + } + if (p != null) { + boolean init = false; + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) + n = MAXIMUM_CAPACITY; + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + int sc = sizeCtl; + boolean collide = false; + if (n > sc && + UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == null) { + init = true; + Node[] tab = new Node[n]; + int mask = n - 1; + while (p != null) { + int j = p.hash & mask; + Node next = p.next; + Node q = p.next = tabAt(tab, j); + setTabAt(tab, j, p); + if (!collide && q != null && q.hash == p.hash) + collide = true; + p = next; + } + table = tab; + counter.add(size); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + if (collide) { // rescan and convert to TreeBins + Node[] tab = table; + for (int i = 0; i < tab.length; ++i) { + int c = 0; + for (Node e = tabAt(tab, i); e != null; e = e.next) { + if (++c > TREE_THRESHOLD && + (e.key instanceof Comparable)) { + replaceWithTreeBin(tab, i, e.key); + break; + } + } + } + } + } + if (!init) { // Can only happen if unsafely published. + while (p != null) { + internalPut(p.key, p.val); + p = p.next; + } + } + } + } + + + // ------------------------------------------------------- + + // Sams + /** Interface describing a void action of one argument */ + public interface Action { void apply(A a); } + /** Interface describing a void action of two arguments */ + public interface BiAction { void apply(A a, B b); } + /** Interface describing a function of one argument */ + public interface Generator { T apply(); } + /** Interface describing a function mapping its argument to a double */ + public interface ObjectToDouble { double apply(A a); } + /** Interface describing a function mapping its argument to a long */ + public interface ObjectToLong { long apply(A a); } + /** Interface describing a function mapping its argument to an int */ + public interface ObjectToInt {int apply(A a); } + /** Interface describing a function mapping two arguments to a double */ + public interface ObjectByObjectToDouble { double apply(A a, B b); } + /** Interface describing a function mapping two arguments to a long */ + public interface ObjectByObjectToLong { long apply(A a, B b); } + /** Interface describing a function mapping two arguments to an int */ + public interface ObjectByObjectToInt {int apply(A a, B b); } + /** Interface describing a function mapping a double to a double */ + public interface DoubleToDouble { double apply(double a); } + /** Interface describing a function mapping a long to a long */ + public interface LongToLong { long apply(long a); } + /** Interface describing a function mapping an int to an int */ + public interface IntToInt { int apply(int a); } + /** Interface describing a function mapping two doubles to a double */ + public interface DoubleByDoubleToDouble { double apply(double a, double b); } + /** Interface describing a function mapping two longs to a long */ + public interface LongByLongToLong { long apply(long a, long b); } + /** Interface describing a function mapping two ints to an int */ + public interface IntByIntToInt { int apply(int a, int b); } + + + /* ----------------Views -------------- */ + + /** + * Base class for views. + */ + static abstract class CHMView { + final ConcurrentHashMapV8 map; + CHMView(ConcurrentHashMapV8 map) { this.map = map; } + + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMapV8 getMap() { return map; } + + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + public final void clear() { map.clear(); } + + // implementations below rely on concrete classes supplying these + abstract public Iterator iterator(); + abstract public boolean contains(Object o); + abstract public boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = it.next(); + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") public final T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)it.next(); + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + public final int hashCode() { + int h = 0; + for (Iterator it = iterator(); it.hasNext();) + h += it.next().hashCode(); + return h; + } + + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); + } + + public final boolean containsAll(Collection c) { + if (c != this) { + for (Iterator it = c.iterator(); it.hasNext();) { + Object e = it.next(); + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. See + * {@link #keySet}, {@link #keySet(Object)}, {@link #newKeySet()}, + * {@link #newKeySet(int)}. + */ + public static class KeySetView extends CHMView implements Set, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMapV8 map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported. + */ + public V getMappedValue() { return value; } + + // implement Set API + + public boolean contains(Object o) { return map.containsKey(o); } + public boolean remove(Object o) { return map.remove(o) != null; } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the keys of this map + */ + public Iterator iterator() { return new KeyIterator(map); } + public boolean add(K e) { + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + if (e == null) + throw new NullPointerException(); + return map.internalPutIfAbsent(e, v) == null; + } + public boolean addAll(Collection c) { + boolean added = false; + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + for (K e : c) { + if (e == null) + throw new NullPointerException(); + if (map.internalPutIfAbsent(e, v) == null) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values}, + * + *

The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public static final class ValuesView extends CHMView + implements Collection { + ValuesView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { return map.containsValue(o); } + public final boolean remove(Object o) { + if (o != null) { + Iterator it = new ValueIterator(map); + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the values of this map + */ + public final Iterator iterator() { + return new ValueIterator(map); + } + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet}. + */ + public static final class EntrySetView extends CHMView + implements Set> { + EntrySetView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { + Object k, v, r; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); + } + public final boolean remove(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the entries of this map + */ + public final Iterator> iterator() { + return new EntryIterator(map); + } + + public final boolean add(Entry e) { + K key = e.getKey(); + V value = e.getValue(); + if (key == null || value == null) + throw new NullPointerException(); + return map.internalPut(key, value) == null; + } + public final boolean addAll(Collection> c) { + boolean added = false; + for (Entry e : c) { + if (add(e)) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long counterOffset; + private static final long sizeCtlOffset; + private static final long ABASE; + private static final int ASHIFT; + + static { + int ss; + try { + UNSAFE = getUnsafe(); + Class k = ConcurrentHashMapV8.class; + counterOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("counter")); + sizeCtlOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("sizeCtl")); + Class sc = Node[].class; + ABASE = UNSAFE.arrayBaseOffset(sc); + ss = UNSAFE.arrayIndexScale(sc); + } catch (Exception e) { + throw new Error(e); + } + if ((ss & (ss-1)) != 0) + throw new Error("data type scale not a power of two"); + ASHIFT = 31 - Integer.numberOfLeadingZeros(ss); + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java new file mode 100644 index 0000000..22d0cbc --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java @@ -0,0 +1,203 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.9 version. + +package org.jruby.ext.thread_safe.jsr166e; +import java.util.concurrent.atomic.AtomicLong; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectInputStream; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java new file mode 100644 index 0000000..8651cdc --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java @@ -0,0 +1,342 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.5 version. + +package org.jruby.ext.thread_safe.jsr166e; +import java.util.Random; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long baseOffset; + private static final long busyOffset; + static { + try { + UNSAFE = getUnsafe(); + Class sk = Striped64.class; + baseOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + busyOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("busy")); + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } + +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java new file mode 100644 index 0000000..4e1e33a --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java @@ -0,0 +1,3800 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on the 1.79 version. + +package org.jruby.ext.thread_safe.jsr166e.nounsafe; + +import org.jruby.RubyClass; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.exceptions.RaiseException; +import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMap; +import org.jruby.ext.thread_safe.jsr166y.ThreadLocalRandom; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Enumeration; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import java.io.Serializable; + +/** + * A hash table supporting full concurrency of retrievals and + * high expected concurrency for updates. This class obeys the + * same functional specification as {@link java.util.Hashtable}, and + * includes versions of methods corresponding to each method of + * {@code Hashtable}. However, even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with {@code Hashtable} in programs that rely on its + * thread safety but not on its synchronization details. + * + *

Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently completed update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * happens-before relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do not throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. + * + *

The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. + * + *

A {@link Set} projection of a ConcurrentHashMapV8 may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + *

A ConcurrentHashMapV8 can be used as scalable frequency map (a + * form of histogram or multiset) by using {@link LongAdder} values + * and initializing via {@link #computeIfAbsent}. For example, to add + * a count to a {@code ConcurrentHashMapV8 freqs}, you + * can use {@code freqs.computeIfAbsent(k -> new + * LongAdder()).increment();} + * + *

This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} + * interfaces. + * + *

Like {@link Hashtable} but unlike {@link HashMap}, this class + * does not allow {@code null} to be used as a key or value. + * + *

ConcurrentHashMapV8s support parallel operations using the {@link + * ForkJoinPool#commonPool}. (Tasks that may be used in other contexts + * are available in class {@link ForkJoinTasks}). These operations are + * designed to be safely, and often sensibly, applied even with maps + * that are being concurrently updated by other threads; for example, + * when computing a snapshot summary of the values in a shared + * registry. There are three kinds of operation, each with four + * forms, accepting functions with Keys, Values, Entries, and (Key, + * Value) arguments and/or return values. (The first three forms are + * also available via the {@link #keySet()}, {@link #values()} and + * {@link #entrySet()} views). Because the elements of a + * ConcurrentHashMapV8 are not ordered in any particular way, and may be + * processed in different orders in different parallel executions, the + * correctness of supplied functions should not depend on any + * ordering, or on any other objects or values that may transiently + * change while computation is in progress; and except for forEach + * actions, should ideally be side-effect-free. + * + *

+ * + *

The concurrency properties of bulk operations follow + * from those of ConcurrentHashMapV8: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + *

Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + *

Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + *

Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + *

Parallel speedups for bulk operations compared to sequential + * processing are common but not guaranteed. Operations involving + * brief functions on small maps may execute more slowly than + * sequential loops if the underlying work to parallelize the + * computation is more expensive than the computation itself. + * Similarly, parallelization may not lead to much actual parallelism + * if all processors are busy performing unrelated tasks. + * + *

All arguments to all task methods must be non-null. + * + *

jsr166e note: During transition, this class + * uses nested functional interfaces with different names but the + * same forms as those expected for JDK8. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMapV8 + implements ConcurrentMap, Serializable, ConcurrentHashMap { + private static final long serialVersionUID = 7249069246763182397L; + + /** + * A partitionable iterator. A Spliterator can be traversed + * directly, but can also be partitioned (before traversal) by + * creating another Spliterator that covers a non-overlapping + * portion of the elements, and so may be amenable to parallel + * execution. + * + *

This interface exports a subset of expected JDK8 + * functionality. + * + *

Sample usage: Here is one (of the several) ways to compute + * the sum of the values held in a map using the ForkJoin + * framework. As illustrated here, Spliterators are well suited to + * designs in which a task repeatedly splits off half its work + * into forked subtasks until small enough to process directly, + * and then joins these subtasks. Variants of this style can also + * be used in completion-based designs. + * + *

+     * {@code ConcurrentHashMapV8 m = ...
+     * // split as if have 8 * parallelism, for load balance
+     * int n = m.size();
+     * int p = aForkJoinPool.getParallelism() * 8;
+     * int split = (n < p)? n : p;
+     * long sum = aForkJoinPool.invoke(new SumValues(m.valueSpliterator(), split, null));
+     * // ...
+     * static class SumValues extends RecursiveTask {
+     *   final Spliterator s;
+     *   final int split;             // split while > 1
+     *   final SumValues nextJoin;    // records forked subtasks to join
+     *   SumValues(Spliterator s, int depth, SumValues nextJoin) {
+     *     this.s = s; this.depth = depth; this.nextJoin = nextJoin;
+     *   }
+     *   public Long compute() {
+     *     long sum = 0;
+     *     SumValues subtasks = null; // fork subtasks
+     *     for (int s = split >>> 1; s > 0; s >>>= 1)
+     *       (subtasks = new SumValues(s.split(), s, subtasks)).fork();
+     *     while (s.hasNext())        // directly process remaining elements
+     *       sum += s.next();
+     *     for (SumValues t = subtasks; t != null; t = t.nextJoin)
+     *       sum += t.join();         // collect subtask results
+     *     return sum;
+     *   }
+     * }
+     * }
+ */ + public static interface Spliterator extends Iterator { + /** + * Returns a Spliterator covering approximately half of the + * elements, guaranteed not to overlap with those subsequently + * returned by this Spliterator. After invoking this method, + * the current Spliterator will not produce any of + * the elements of the returned Spliterator, but the two + * Spliterators together will produce all of the elements that + * would have been produced by this Spliterator had this + * method not been called. The exact number of elements + * produced by the returned Spliterator is not guaranteed, and + * may be zero (i.e., with {@code hasNext()} reporting {@code + * false}) if this Spliterator cannot be further split. + * + * @return a Spliterator covering approximately half of the + * elements + * @throws IllegalStateException if this Spliterator has + * already commenced traversing elements + */ + Spliterator split(); + } + + + /* + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * Each key-value mapping is held in a Node. Because Node fields + * can contain special values, they are defined using plain Object + * types. Similarly in turn, all internal methods that use them + * work off Object types. And similarly, so do the internal + * methods of auxiliary iterator and view classes. All public + * generic typed methods relay in/out of these internal methods, + * supplying null-checks and casts as needed. This also allows + * many of the public methods to be factored into a smaller number + * of internal methods (although sadly not so for the five + * variants of put-related operations). The validation-based + * approach explained below leads to a lot of code sprawl because + * retry-control precludes factoring into smaller methods. + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. The lists of nodes within bins + * are always accurately traversable under volatile reads, so long + * as lookups check hash code and non-nullness of value before + * checking key equality. + * + * We use the top two bits of Node hash fields for control + * purposes -- they are available anyway because of addressing + * constraints. As explained further below, these top bits are + * used as follows: + * 00 - Normal + * 01 - Locked + * 11 - Locked and may have a thread waiting for lock + * 10 - Node is a forwarding node + * + * The lower 30 bits of each Node's hash field contain a + * transformation of the key's hash code, except for forwarding + * nodes, for which the lower bits are zero (and so always have + * hash field == MOVED). + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Blocking support for these locks relies on the builtin + * "synchronized" monitors. However, we also need a tryLock + * construction, so we overlay these by using bits of the Node + * hash field for lock control (see above), and so normally use + * builtin monitors only for blocking and signalling using + * wait/notifyAll constructions. See Node.tryAwaitLock. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). However, + * operations that only conditionally update may inspect nodes + * until the point of update. This is a converse of sorts to the + * lazy locking technique described by Herlihy & Shavit. + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes. Also, although we guard + * against the worst effects of this (see method spread), sets of + * hashes may differ only in bits that do not impact their bin + * index for a given power-of-two mask. So we use a secondary + * strategy that applies when the number of nodes in a bin exceeds + * a threshold, and at least one of the keys implements + * Comparable. These TreeBins use a balanced tree to hold nodes + * (a specialized form of red-black trees), bounding search time + * to O(log N). Each search step in a TreeBin is around twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Only a single + * thread performs the resize (using field "sizeCtl", to arrange + * exclusion), but the table otherwise remains usable for reads + * and updates. Resizing proceeds by transferring bins, one by + * one, from the table to the next table. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. On + * average, only about one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage collectable as + * soon as they are no longer referenced by any reader thread that + * may be in the midst of concurrently traversing table. Upon + * transfer, the old table bin contains only a special forwarding + * node (with hash field "MOVED") that contains the next table as + * its key. On encountering a forwarding node, access and update + * operations restart, using the new table. + * + * Each bin transfer requires its bin lock. However, unlike other + * cases, a transfer can skip a bin if it fails to acquire its + * lock, and revisit it later (unless it is a TreeBin). Method + * rebuild maintains a buffer of TRANSFER_BUFFER_SIZE bins that + * have been skipped because of failure to acquire a lock, and + * blocks only if none are available (i.e., only very rarely). + * The transfer operation must also ensure that all accessible + * bins in both the old and new table are usable by any traversal. + * When there are no lock acquisition failures, this is arranged + * simply by proceeding from the last bin (table.length - 1) up + * towards the first. Upon seeing a forwarding node, traversals + * (see class Iter) arrange to move to the new table + * without revisiting nodes. However, when any node is skipped + * during a transfer, all earlier table bins may have become + * visible, so are initialized with a reverse-forwarding node back + * to the old table until the new ones are established. (This + * sometimes requires transiently locking a forwarding node, which + * is possible under the above encoding.) These more expensive + * mechanics trigger only when necessary. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a LongAdder, which avoids + * contention on updates but can encounter cache thrashing if read + * too frequently during concurrent access. To avoid reading so + * often, resizing is attempted either when a bin lock is + * contended, or upon adding to a bin already holding two or more + * nodes (checked before adding in the xIfAbsent methods, after + * adding in others). Under uniform hash distributions, the + * probability of this occurring at threshold is around 13%, + * meaning that only about 1 in 8 puts check threshold (and after + * resizing, many fewer do so). But this approximation has high + * variance for small table sizes, so we check on any collision + * for sizes <= 64. The bulk putAll operation further reduces + * contention by only committing count updates upon these size + * checks. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments refering to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + */ + + /* ---------------- Constants -------------- */ + + /** + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. + */ + private static final int DEFAULT_CAPACITY = 16; + + /** + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. + */ + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. + */ + private static final float LOAD_FACTOR = 0.75f; + + /** + * The buffer size for skipped bins during transfers. The + * value is arbitrary but should be large enough to avoid + * most locking stalls during resizes. + */ + private static final int TRANSFER_BUFFER_SIZE = 32; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. The value reflects the approximate break-even point for + * using tree-based operations. + * Note that Doug's version defaults to 8, but when dealing with + * Ruby objects it is actually beneficial to avoid TreeNodes + * as long as possible as it usually means going into Ruby land. + */ + private static final int TREE_THRESHOLD = 16; + + /* + * Encodings for special uses of Node hash fields. See above for + * explanation. + */ + static final int MOVED = 0x80000000; // hash field for forwarding nodes + static final int LOCKED = 0x40000000; // set/tested only as a bit + static final int WAITING = 0xc0000000; // both bits set/tested together + static final int HASH_BITS = 0x3fffffff; // usable bits of normal node hash + + /* ---------------- Fields -------------- */ + + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile AtomicReferenceArray table; + + /** + * The counter maintaining number of elements. + */ + private transient LongAdder counter; + + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized. Otherwise, when table is + * null, holds the initial table size to use upon creation, or 0 + * for default. After initialization, holds the next element count + * value upon which to resize the table. + */ + private transient volatile int sizeCtl; + + // views + private transient KeySetView keySet; + private transient ValuesView values; + private transient EntrySetView entrySet; + + /** For serialization compatibility. Null unless serialized; see below */ + private Segment[] segments; + + static AtomicIntegerFieldUpdater SIZE_CTRL_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ConcurrentHashMapV8.class, "sizeCtl"); + + /* ---------------- Table element access -------------- */ + + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. Uses are + * null checked by callers, and implicitly bounds-checked, relying + * on the invariants that tab arrays have non-zero size, and all + * indices are masked with (tab.length - 1) which is never + * negative and always less than length. Note that, to be correct + * wrt arbitrary concurrency errors by users, bounds checks must + * operate on local variables, which accounts for some odd-looking + * inline assignments below. + */ + + static final Node tabAt(AtomicReferenceArray tab, int i) { // used by Iter + return tab.get(i); + } + + private static final boolean casTabAt(AtomicReferenceArray tab, int i, Node c, Node v) { + return tab.compareAndSet(i, c, v); + } + + private static final void setTabAt(AtomicReferenceArray tab, int i, Node v) { + tab.set(i, v); + } + + /* ---------------- Nodes -------------- */ + + /** + * Key-value entry. Note that this is never exported out as a + * user-visible Map.Entry (see MapEntry below). Nodes with a hash + * field of MOVED are special, and do not contain user keys or + * values. Otherwise, keys are never null, and null val fields + * indicate that a node is in the process of being deleted or + * created. For purposes of read-only access, a key may be read + * before a val, but can only be used after checking val to be + * non-null. + */ + static class Node { + volatile int hash; + final Object key; + volatile Object val; + volatile Node next; + + static AtomicIntegerFieldUpdater HASH_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Node.class, "hash"); + + Node(int hash, Object key, Object val, Node next) { + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + /** CompareAndSet the hash field */ + final boolean casHash(int cmp, int val) { + return HASH_UPDATER.compareAndSet(this, cmp, val); + } + + /** The number of spins before blocking for a lock */ + static final int MAX_SPINS = + Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; + + /** + * Spins a while if LOCKED bit set and this node is the first + * of its bin, and then sets WAITING bits on hash field and + * blocks (once) if they are still set. It is OK for this + * method to return even if lock is not available upon exit, + * which enables these simple single-wait mechanics. + * + * The corresponding signalling operation is performed within + * callers: Upon detecting that WAITING has been set when + * unlocking lock (via a failed CAS from non-waiting LOCKED + * state), unlockers acquire the sync lock and perform a + * notifyAll. + * + * The initial sanity check on tab and bounds is not currently + * necessary in the only usages of this method, but enables + * use in other future contexts. + */ + final void tryAwaitLock(AtomicReferenceArray tab, int i) { + if (tab != null && i >= 0 && i < tab.length()) { // sanity check + int r = ThreadLocalRandom.current().nextInt(); // randomize spins + int spins = MAX_SPINS, h; + while (tabAt(tab, i) == this && ((h = hash) & LOCKED) != 0) { + if (spins >= 0) { + r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift + if (r >= 0 && --spins == 0) + Thread.yield(); // yield before block + } + else if (casHash(h, h | WAITING)) { + synchronized (this) { + if (tabAt(tab, i) == this && + (hash & WAITING) == WAITING) { + try { + wait(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + else + notifyAll(); // possibly won race vs signaller + } + break; + } + } + } + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode extends Node { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, Object key, Object val, Node next, TreeNode parent) { + super(hash, key, val, next); + this.parent = parent; + } + } + + /** + * A specialized form of red-black tree for use in bins + * whose size exceeds a threshold. + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable + * for the same T, so we cannot invoke compareTo among them. To + * handle this, the tree is ordered primarily by hash value, then + * by getClass().getName() order, and then by Comparator order + * among elements of the same class. On lookup at a node, if + * elements are not comparable or compare as 0, both left and + * right children may need to be searched in the case of tied hash + * values. (This corresponds to the full list search that would be + * necessary if all elements were non-Comparable and had tied + * hashes.) The red-black balancing code is updated from + * pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also maintain a separate locking discipline than + * regular bins. Because they are forwarded via special MOVED + * nodes at bin heads (which can never change once established), + * we cannot use those nodes as locks. Instead, TreeBin + * extends AbstractQueuedSynchronizer to support a simple form of + * read-write lock. For update operations and table validation, + * the exclusive form of lock behaves in the same way as bin-head + * locks. However, lookups use shared read-lock mechanics to allow + * multiple readers in the absence of writers. Additionally, + * these lookups do not ever block: While the lock is not + * available, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. (These cases are not fast, + * but maximize aggregate expected throughput.) The AQS mechanics + * for doing this are straightforward. The lock state is held as + * AQS getState(). Read counts are negative; the write count (1) + * is positive. There are no signalling preferences among readers + * and writers. Since we don't need to export full Lock API, we + * just override the minimal AQS methods and use them directly. + */ + static final class TreeBin extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 2249069246763182397L; + transient TreeNode root; // root of tree + transient TreeNode first; // head of next-pointer list + + /* AQS overrides */ + public final boolean isHeldExclusively() { return getState() > 0; } + public final boolean tryAcquire(int ignore) { + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + public final boolean tryRelease(int ignore) { + setExclusiveOwnerThread(null); + setState(0); + return true; + } + public final int tryAcquireShared(int ignore) { + for (int c;;) { + if ((c = getState()) > 0) + return -1; + if (compareAndSetState(c, c -1)) + return 1; + } + } + public final boolean tryReleaseShared(int ignore) { + int c; + do {} while (!compareAndSetState(c = getState(), c + 1)); + return c == -1; + } + + /** From CLR */ + private void rotateLeft(TreeNode p) { + if (p != null) { + TreeNode r = p.right, pp, rl; + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + root = r; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + } + + /** From CLR */ + private void rotateRight(TreeNode p) { + if (p != null) { + TreeNode l = p.left, pp, lr; + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + root = l; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + } + + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, Object k, TreeNode p) { + return getTreeNode(h, (RubyObject)k, p); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, RubyObject k, TreeNode p) { + RubyClass c = k.getMetaClass(); boolean kNotComparable = !k.respondsTo("<=>"); + while (p != null) { + int dir, ph; RubyObject pk; RubyClass pc; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = (RubyClass)pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pl, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + // try to continue iterating on the left side + else if ((pl = p.left) != null && h <= pl.hash) + dir = -1; + else // no matching node found + return null; + } + } + } + else + dir = (h < ph) ? -1 : 1; + p = (dir > 0) ? p.right : p.left; + } + return null; + } + + int rubyCompare(RubyObject l, RubyObject r) { + ThreadContext context = l.getMetaClass().getRuntime().getCurrentContext(); + IRubyObject result; + try { + result = l.callMethod(context, "<=>", r); + } catch (RaiseException e) { + // handle objects "lying" about responding to <=>, ie: an Array containing non-comparable keys + if (context.runtime.getNoMethodError().isInstance(e.getException())) { + return 0; + } + throw e; + } + + return result.isNil() ? 0 : RubyNumeric.num2int(result.convertToInteger()); + } + + /** + * Wrapper for getTreeNode used by CHM.get. Tries to obtain + * read-lock to call getTreeNode, but during failure to get + * lock, searches along next links. + */ + final Object getValue(int h, Object k) { + Node r = null; + int c = getState(); // Must read lock state first + for (Node e = first; e != null; e = e.next) { + if (c <= 0 && compareAndSetState(c, c - 1)) { + try { + r = getTreeNode(h, k, root); + } finally { + releaseShared(0); + } + break; + } + else if ((e.hash & HASH_BITS) == h && k.equals(e.key)) { + r = e; + break; + } + else + c = getState(); + } + return r == null ? null : r.val; + } + + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, Object k, Object v) { + return putTreeNode(h, (RubyObject)k, v); + } + + /** + * Finds or adds a node. + * @return null if added + */ + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, RubyObject k, Object v) { + RubyClass c = k.getMetaClass(); + boolean kNotComparable = !k.respondsTo("<=>"); + TreeNode pp = root, p = null; + int dir = 0; + while (pp != null) { // find existing node or leaf to insert at + int ph; RubyObject pk; RubyClass pc; + p = pp; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + else // continue descending down the left subtree + dir = -1; + } + } + } + else + dir = (h < ph) ? -1 : 1; + pp = (dir > 0) ? p.right : p.left; + } + + TreeNode f = first; + TreeNode x = first = new TreeNode(h, (Object)k, v, f, p); + if (p == null) + root = x; + else { // attach and rebalance; adapted from CLR + TreeNode xp, xpp; + if (f != null) + f.prev = x; + if (dir <= 0) + p.left = x; + else + p.right = x; + x.red = true; + while (x != null && (xp = x.parent) != null && xp.red && + (xpp = xp.parent) != null) { + TreeNode xppl = xpp.left; + if (xp == xppl) { + TreeNode y = xpp.right; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + rotateLeft(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateRight(xpp); + } + } + } + } + else { + TreeNode y = xppl; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + rotateRight(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateLeft(xpp); + } + } + } + } + } + TreeNode r = root; + if (r != null && r.red) + r.red = false; + } + return null; + } + + /** + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + */ + final void deleteTreeNode(TreeNode p) { + TreeNode next = (TreeNode)p.next; // unlink traversal pointers + TreeNode pred = p.prev; + if (pred == null) + first = next; + else + pred.next = next; + if (next != null) + next.prev = pred; + TreeNode replacement; + TreeNode pl = p.left; + TreeNode pr = p.right; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + replacement = sr; + } + else + replacement = (pl != null) ? pl : pr; + TreeNode pp = p.parent; + if (replacement == null) { + if (pp == null) { + root = null; + return; + } + replacement = p; + } + else { + replacement.parent = pp; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + if (!p.red) { // rebalance, from CLR + TreeNode x = replacement; + while (x != null) { + TreeNode xp, xpl; + if (x.red || (xp = x.parent) == null) { + x.red = false; + break; + } + if (x == (xpl = xp.left)) { + TreeNode sib = xp.right; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateLeft(xp); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + sib.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + sib.red = true; + rotateRight(sib); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sr = sib.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + rotateLeft(xp); + } + x = root; + } + } + } + else { // symmetric + TreeNode sib = xpl; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateRight(xp); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + sib.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + sib.red = true; + rotateLeft(sib); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sl = sib.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + rotateRight(xp); + } + x = root; + } + } + } + } + } + if (p == replacement && (pp = p.parent) != null) { + if (p == pp.left) // detach pointers + pp.left = null; + else if (p == pp.right) + pp.right = null; + p.parent = null; + } + } + } + + /* ---------------- Collision reduction methods -------------- */ + + /** + * Spreads higher bits to lower, and also forces top 2 bits to 0. + * Because the table uses power-of-two masking, sets of hashes + * that vary only in bits above the current mask will always + * collide. (Among known examples are sets of Float keys holding + * consecutive whole numbers in small tables.) To counter this, + * we apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed across bits (so don't benefit + * from spreading), and because we use trees to handle large sets + * of collisions in bins, we don't need excessively high quality. + */ + private static final int spread(int h) { + h ^= (h >>> 18) ^ (h >>> 12); + return (h ^ (h >>> 10)) & HASH_BITS; + } + + /** + * Replaces a list bin with a tree bin. Call only when locked. + * Fails to replace if the given key is non-comparable or table + * is, or needs, resizing. + */ + private final void replaceWithTreeBin(AtomicReferenceArray tab, int index, Object key) { + if ((key instanceof Comparable) && + (tab.length() >= MAXIMUM_CAPACITY || counter.sum() < (long)sizeCtl)) { + TreeBin t = new TreeBin(); + for (Node e = tabAt(tab, index); e != null; e = e.next) + t.putTreeNode(e.hash & HASH_BITS, e.key, e.val); + setTabAt(tab, index, new Node(MOVED, t, null, null)); + } + } + + /* ---------------- Internal access and update methods -------------- */ + + /** Implementation for get and containsKey */ + private final Object internalGet(Object k) { + int h = spread(k.hashCode()); + retry: for (AtomicReferenceArray tab = table; tab != null;) { + Node e, p; Object ek, ev; int eh; // locals to read fields once + for (e = tabAt(tab, (tab.length() - 1) & h); e != null; e = e.next) { + if ((eh = e.hash) == MOVED) { + if ((ek = e.key) instanceof TreeBin) // search TreeBin + return ((TreeBin)ek).getValue(h, k); + else { // restart with new table + tab = (AtomicReferenceArray)ek; + continue retry; + } + } + else if ((eh & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + } + break; + } + return null; + } + + /** + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. + */ + private final Object internalReplace(Object k, Object v, Object cv) { + int h = spread(k.hashCode()); + Object oldVal = null; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null || + (f = tabAt(tab, i = (tab.length() - 1) & h)) == null) + break; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + boolean deleted = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) { + Object pv = p.val; + if (cv == null || cv == pv || cv.equals(pv)) { + oldVal = pv; + if ((p.val = v) == null) { + deleted = true; + t.deleteTreeNode(p); + } + } + } + } + } finally { + t.release(0); + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) != h && f.next == null) // precheck + break; // rules out possible existence + else if ((fh & LOCKED) != 0) { + checkForResize(); // try resizing if can't get lock + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + boolean validated = false; + boolean deleted = false; + try { + if (tabAt(tab, i) == f) { + validated = true; + for (Node e = f, pred = null;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + ((ev = e.val) != null) && + ((ek = e.key) == k || k.equals(ek))) { + if (cv == null || cv == ev || cv.equals(ev)) { + oldVal = ev; + if ((e.val = v) == null) { + deleted = true; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + } + return oldVal; + } + + /* + * Internal versions of the six insertion methods, each a + * little more complicated than the last. All have + * the same basic structure as the first (internalPut): + * 1. If table uninitialized, create + * 2. If bin empty, try to CAS new node + * 3. If bin stale, use new table + * 4. if bin converted to TreeBin, validate and relay to TreeBin methods + * 5. Lock and validate; if valid, scan and add or update + * + * The others interweave other checks and/or alternative actions: + * * Plain put checks for and performs resize after insertion. + * * putIfAbsent prescans for mapping without lock (and fails to add + * if present), which also makes pre-emptive resize checks worthwhile. + * * computeIfAbsent extends form used in putIfAbsent with additional + * mechanics to deal with, calls, potential exceptions and null + * returns from function call. + * * compute uses the same function-call mechanics, but without + * the prescans + * * merge acts as putIfAbsent in the absent case, but invokes the + * update function if present + * * putAll attempts to pre-allocate enough table space + * and more lazily performs count updates and checks. + * + * Someday when details settle down a bit more, it might be worth + * some factoring to reduce sprawl. + */ + + /** Implementation for put */ + private final Object internalPut(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) { + oldVal = p.val; + p.val = v; + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { // needed in case equals() throws + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { // unlock and signal if needed + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for putIfAbsent */ + private final Object internalPutIfAbsent(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) + oldVal = p.val; + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { // at least 2 nodes -- search and maybe resize + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for computeIfAbsent */ + private final Object internalComputeIfAbsent(K k, + Fun mf) { + int h = spread(k.hashCode()); + Object val = null; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + count = 1; + try { + if ((val = mf.apply(k)) != null) + node.val = val; + } finally { + if (val == null) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean added = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + val = p.val; + else if ((val = mf.apply(k)) != null) { + added = true; + count = 2; + t.putTreeNode(h, k, val); + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (!added) + return val; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + boolean added = false; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + if ((val = mf.apply(k)) != null) { + added = true; + last.next = new Node(h, k, val, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (!added) + return val; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + } + if (val != null) { + counter.add(1L); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for compute */ + @SuppressWarnings("unchecked") private final Object internalCompute + (K k, boolean onlyIfPresent, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (onlyIfPresent) + break; + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + try { + count = 1; + if ((val = mf.apply(k, null)) != null) { + node.val = val; + delta = 1; + } + } finally { + if (delta == 0) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + Object pv; + if (p == null) { + if (onlyIfPresent) + break; + pv = null; + } else + pv = p.val; + if ((val = mf.apply(k, (V)pv)) != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply(k, (V)ev); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + if (!onlyIfPresent && (val = mf.apply(k, null)) != null) { + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length() <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for merge */ + @SuppressWarnings("unchecked") private final Object internalMerge + (K k, V v, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + delta = 1; + val = v; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + val = (p == null) ? v : mf.apply((V)p.val, v); + if (val != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply((V)ev, v); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = v; + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length() <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for putAll */ + private final void internalPutAll(Map m) { + tryPresize(m.size()); + long delta = 0L; // number of uncommitted additions + boolean npe = false; // to throw exception on exit for nulls + try { // to clean up counts on other exceptions + for (Map.Entry entry : m.entrySet()) { + Object k, v; + if (entry == null || (k = entry.getKey()) == null || + (v = entry.getValue()) == null) { + npe = true; + break; + } + int h = spread(k.hashCode()); + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null){ + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + ++delta; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + p.val = v; + else { + t.putTreeNode(h, k, v); + ++delta; + } + } + } finally { + t.release(0); + } + if (validated) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); + delta = 0L; + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + int count = 0; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + ++delta; + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (count > 1) { + counter.add(delta); + delta = 0L; + checkForResize(); + } + break; + } + } + } + } + } finally { + if (delta != 0) + counter.add(delta); + } + if (npe) + throw new NullPointerException(); + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 + */ + private static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private final AtomicReferenceArray initTable() { + AtomicReferenceArray tab; int sc; + while ((tab = table) == null) { + if ((sc = sizeCtl) < 0) + Thread.yield(); // lost initialization race; just spin + else if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if ((tab = table) == null) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + tab = table = new AtomicReferenceArray(n); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; + } + + /** + * If table is too small and not already resizing, creates next + * table and transfers bins. Rechecks occupancy after a transfer + * to see if another resize is already needed because resizings + * are lagging additions. + */ + private final void checkForResize() { + AtomicReferenceArray tab; int n, sc; + while ((tab = table) != null && + (n = tab.length()) < MAXIMUM_CAPACITY && + (sc = sizeCtl) >= 0 && counter.sum() >= (long)sc && + SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (tab == table) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private final void tryPresize(int size) { + int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + AtomicReferenceArray tab = table; int n; + if (tab == null || (n = tab.length()) == 0) { + n = (sc > c) ? sc : c; + if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == tab) { + table = new AtomicReferenceArray(n); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) + break; + else if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == tab) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + } + + /* + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + * + * @return the new table + */ + private static final AtomicReferenceArray rebuild(AtomicReferenceArray tab) { + int n = tab.length(); + AtomicReferenceArray nextTab = new AtomicReferenceArray(n << 1); + Node fwd = new Node(MOVED, nextTab, null, null); + int[] buffer = null; // holds bins to revisit; null until needed + Node rev = null; // reverse forwarder; null until needed + int nbuffered = 0; // the number of bins in buffer list + int bufferIndex = 0; // buffer index of current buffered bin + int bin = n - 1; // current non-buffered bin or -1 if none + + for (int i = bin;;) { // start upwards sweep + int fh; Node f; + if ((f = tabAt(tab, i)) == null) { + if (bin >= 0) { // Unbuffered; no lock needed (or available) + if (!casTabAt(tab, i, f, fwd)) + continue; + } + else { // transiently use a locked forwarding node + Node g = new Node(MOVED|LOCKED, nextTab, null, null); + if (!casTabAt(tab, i, f, g)) + continue; + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + setTabAt(tab, i, fwd); + if (!g.casHash(MOVED|LOCKED, MOVED)) { + g.hash = MOVED; + synchronized (g) { g.notifyAll(); } + } + } + } + else if ((fh = f.hash) == MOVED) { + Object fk = f.key; + if (fk instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + splitTreeBin(nextTab, i, t); + setTabAt(tab, i, fwd); + } + } finally { + t.release(0); + } + if (!validated) + continue; + } + } + else if ((fh & LOCKED) == 0 && f.casHash(fh, fh|LOCKED)) { + boolean validated = false; + try { // split to lo and hi lists; copying as needed + if (tabAt(tab, i) == f) { + validated = true; + splitBin(nextTab, i, f); + setTabAt(tab, i, fwd); + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (!validated) + continue; + } + else { + if (buffer == null) // initialize buffer for revisits + buffer = new int[TRANSFER_BUFFER_SIZE]; + if (bin < 0 && bufferIndex > 0) { + int j = buffer[--bufferIndex]; + buffer[bufferIndex] = i; + i = j; // swap with another bin + continue; + } + if (bin < 0 || nbuffered >= TRANSFER_BUFFER_SIZE) { + f.tryAwaitLock(tab, i); + continue; // no other options -- block + } + if (rev == null) // initialize reverse-forwarder + rev = new Node(MOVED, tab, null, null); + if (tabAt(tab, i) != f || (f.hash & LOCKED) == 0) + continue; // recheck before adding to list + buffer[nbuffered++] = i; + setTabAt(nextTab, i, rev); // install place-holders + setTabAt(nextTab, i + n, rev); + } + + if (bin > 0) + i = --bin; + else if (buffer != null && nbuffered > 0) { + bin = -1; + i = buffer[bufferIndex = --nbuffered]; + } + else + return nextTab; + } + } + + /** + * Splits a normal bin with list headed by e into lo and hi parts; + * installs in given table. + */ + private static void splitBin(AtomicReferenceArray nextTab, int i, Node e) { + int bit = nextTab.length() >>> 1; // bit to split on + int runBit = e.hash & bit; + Node lastRun = e, lo = null, hi = null; + for (Node p = e.next; p != null; p = p.next) { + int b = p.hash & bit; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) + lo = lastRun; + else + hi = lastRun; + for (Node p = e; p != lastRun; p = p.next) { + int ph = p.hash & HASH_BITS; + Object pk = p.key, pv = p.val; + if ((ph & bit) == 0) + lo = new Node(ph, pk, pv, lo); + else + hi = new Node(ph, pk, pv, hi); + } + setTabAt(nextTab, i, lo); + setTabAt(nextTab, i + bit, hi); + } + + /** + * Splits a tree bin into lo and hi parts; installs in given table. + */ + private static void splitTreeBin(AtomicReferenceArray nextTab, int i, TreeBin t) { + int bit = nextTab.length() >>> 1; + TreeBin lt = new TreeBin(); + TreeBin ht = new TreeBin(); + int lc = 0, hc = 0; + for (Node e = t.first; e != null; e = e.next) { + int h = e.hash & HASH_BITS; + Object k = e.key, v = e.val; + if ((h & bit) == 0) { + ++lc; + lt.putTreeNode(h, k, v); + } + else { + ++hc; + ht.putTreeNode(h, k, v); + } + } + Node ln, hn; // throw away trees if too small + if (lc <= (TREE_THRESHOLD >>> 1)) { + ln = null; + for (Node p = lt.first; p != null; p = p.next) + ln = new Node(p.hash, p.key, p.val, ln); + } + else + ln = new Node(MOVED, lt, null, null); + setTabAt(nextTab, i, ln); + if (hc <= (TREE_THRESHOLD >>> 1)) { + hn = null; + for (Node p = ht.first; p != null; p = p.next) + hn = new Node(p.hash, p.key, p.val, hn); + } + else + hn = new Node(MOVED, ht, null, null); + setTabAt(nextTab, i + bit, hn); + } + + /** + * Implementation for clear. Steps through each bin, removing all + * nodes. + */ + private final void internalClear() { + long delta = 0L; // negative number of deletions + int i = 0; + AtomicReferenceArray tab = table; + while (tab != null && i < tab.length()) { + int fh; Object fk; + Node f = tabAt(tab, i); + if (f == null) + ++i; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + for (Node p = t.first; p != null; p = p.next) { + if (p.val != null) { // (currently always true) + p.val = null; + --delta; + } + } + t.first = null; + t.root = null; + ++i; + } + } finally { + t.release(0); + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); // opportunistically update count + delta = 0L; + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + for (Node e = f; e != null; e = e.next) { + if (e.val != null) { // (currently always true) + e.val = null; + --delta; + } + } + setTabAt(tab, i, null); + ++i; + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + } + } + if (delta != 0) + counter.add(delta); + } + + /* ----------------Table Traversal -------------- */ + + /** + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators and bulk tasks. + * + * At each step, the iterator snapshots the key ("nextKey") and + * value ("nextVal") of a valid node (i.e., one that, at point of + * snapshot, has a non-null user value). Because val fields can + * change (including to null, indicating deletion), field nextVal + * might not be accurate at point of use, but still maintains the + * weak consistency property of holding a value that was once + * valid. To support iterator.remove, the nextKey field is not + * updated (nulled out) when the iterator cannot advance. + * + * Internal traversals directly access these fields, as in: + * {@code while (it.advance() != null) { process(it.nextKey); }} + * + * Exported iterators must track whether the iterator has advanced + * (in hasNext vs next) (by setting/checking/nulling field + * nextVal), and then extract key, value, or key-value pairs as + * return values of next(). + * + * The iterator visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. + * + * This class extends ForkJoinTask to streamline parallel + * iteration in bulk operations (see BulkTask). This adds only an + * int of space overhead, which is close enough to negligible in + * cases where it is not needed to not worry about it. Because + * ForkJoinTask is Serializable, but iterators need not be, we + * need to add warning suppressions. + */ + @SuppressWarnings("serial") static class Traverser { + final ConcurrentHashMapV8 map; + Node next; // the next entry to use + K nextKey; // cached key field of next + V nextVal; // cached val field of next + AtomicReferenceArray tab; // current table; updated if resized + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + int baseSize; // initial table size + + /** Creates iterator for all entries in the table. */ + Traverser(ConcurrentHashMapV8 map) { + this.map = map; + } + + /** Creates iterator for split() methods */ + Traverser(Traverser it) { + ConcurrentHashMapV8 m; AtomicReferenceArray t; + if ((m = this.map = it.map) == null) + t = null; + else if ((t = it.tab) == null && // force parent tab initialization + (t = it.tab = m.table) != null) + it.baseLimit = it.baseSize = t.length(); + this.tab = t; + this.baseSize = it.baseSize; + it.baseLimit = this.index = this.baseIndex = + ((this.baseLimit = it.baseLimit) + it.baseIndex + 1) >>> 1; + } + + /** + * Advances next; returns nextVal or null if terminated. + * See above for explanation. + */ + final V advance() { + Node e = next; + V ev = null; + outer: do { + if (e != null) // advance past used/skipped node + e = e.next; + while (e == null) { // get to next non-null bin + ConcurrentHashMapV8 m; + AtomicReferenceArray t; int b, i, n; Object ek; // checks must use locals + if ((t = tab) != null) + n = t.length(); + else if ((m = map) != null && (t = tab = m.table) != null) + n = baseLimit = baseSize = t.length(); + else + break outer; + if ((b = baseIndex) >= baseLimit || + (i = index) < 0 || i >= n) + break outer; + if ((e = tabAt(t, i)) != null && e.hash == MOVED) { + if ((ek = e.key) instanceof TreeBin) + e = ((TreeBin)ek).first; + else { + tab = (AtomicReferenceArray)ek; + continue; // restarts due to null val + } + } // visit upper slots if present + index = (i += baseSize) < n ? i : (baseIndex = b + 1); + } + nextKey = (K) e.key; + } while ((ev = (V) e.val) == null); // skip deleted or special nodes + next = e; + return nextVal = ev; + } + + public final void remove() { + Object k = nextKey; + if (k == null && (advance() == null || (k = nextKey) == null)) + throw new IllegalStateException(); + map.internalReplace(k, null, null); + } + + public final boolean hasNext() { + return nextVal != null || advance() != null; + } + + public final boolean hasMoreElements() { return hasNext(); } + public final void setRawResult(Object x) { } + public R getRawResult() { return null; } + public boolean exec() { return true; } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMapV8() { + this.counter = new LongAdder(); + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMapV8(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(); + int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMapV8(Map m) { + this.counter = new LongAdder(); + this.sizeCtl = DEFAULT_CAPACITY; + internalPutAll(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * + * @since 1.6 + */ + public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMapV8(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + if (initialCapacity < concurrencyLevel) // Use at least as many bins + initialCapacity = concurrencyLevel; // as estimated threads + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = (size >= (long)MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + */ + public static KeySetView newKeySet() { + return new KeySetView(new ConcurrentHashMapV8(), + Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @return the new set + */ + public static KeySetView newKeySet(int initialCapacity) { + return new KeySetView(new ConcurrentHashMapV8(initialCapacity), + Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return counter.sum() <= 0L; // ignore transient negative values + } + + /** + * {@inheritDoc} + */ + public int size() { + long n = counter.sum(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); + } + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMapV8 may + * contain more mappings than can be represented as an int. The + * value returned is a snapshot; the actual count may differ if + * there are ongoing concurrent insertions or removals. + * + * @return the number of mappings + */ + public long mappingCount() { + long n = counter.sum(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V get(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalGet(key); + } + + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V getValueOrDefault(Object key, V defaultValue) { + if (key == null) + throw new NullPointerException(); + V v = (V) internalGet(key); + return v == null ? defaultValue : v; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object + * is a key in this table, as determined by the + * {@code equals} method; {@code false} otherwise + * @throws NullPointerException if the specified key is null + */ + public boolean containsKey(Object key) { + if (key == null) + throw new NullPointerException(); + return internalGet(key) != null; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + * @throws NullPointerException if the specified value is null + */ + public boolean containsValue(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return true; + } + return false; + } + + public K findKey(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return it.nextKey; + } + return null; + } + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

The value can be retrieved by calling the {@code get} method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V put(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPut(key, value); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPutIfAbsent(key, value); + } + + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + internalPutAll(m); + } + + /** + * If the specified key is not already associated with a value, + * computes its value using the given mappingFunction and enters + * it into the map unless null. This is equivalent to + *

 {@code
+     * if (map.containsKey(key))
+     *   return map.get(key);
+     * value = mappingFunction.apply(key);
+     * if (value != null)
+     *   map.put(key, value);
+     * return value;}
+ * + * except that the action is performed atomically. If the + * function returns {@code null} no mapping is recorded. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and no mapping is recorded. Some + * attempted update operations on this map by other threads may be + * blocked while computation is in progress, so the computation + * should be short and simple, and must not attempt to update any + * other mappings of this Map. The most appropriate usage is to + * construct a new object serving as an initial mapped value, or + * memoized result, as in: + * + *
 {@code
+     * map.computeIfAbsent(key, new Fun() {
+     *   public V map(K k) { return new Value(f(k)); }});}
+ * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished + */ + @SuppressWarnings("unchecked") public V computeIfAbsent + (K key, Fun mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + return (V)internalComputeIfAbsent(key, mappingFunction); + } + + /** + * If the given key is present, computes a new mapping value given a key and + * its current mapped value. This is equivalent to + *
 {@code
+     *   if (map.containsKey(key)) {
+     *     value = remappingFunction.apply(key, map.get(key));
+     *     if (value != null)
+     *       map.put(key, value);
+     *     else
+     *       map.remove(key);
+     *   }
+     * }
+ * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V computeIfPresent + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, true, remappingFunction); + } + + /** + * Computes a new mapping value given a key and + * its current mapped value (or {@code null} if there is no current + * mapping). This is equivalent to + *
 {@code
+     *   value = remappingFunction.apply(key, map.get(key));
+     *   if (value != null)
+     *     map.put(key, value);
+     *   else
+     *     map.remove(key);
+     * }
+ * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + *
 {@code
+     * Map map = ...;
+     * final String msg = ...;
+     * map.compute(key, new BiFun() {
+     *   public String apply(Key k, String v) {
+     *    return (v == null) ? msg : v + msg;});}}
+ * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V compute + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, false, remappingFunction); + } + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * Otherwise, replace the value with the results of + * the given remapping function. This is equivalent to: + *
 {@code
+     *   if (!map.containsKey(key))
+     *     map.put(value);
+     *   else {
+     *     newValue = remappingFunction.apply(map.get(key), value);
+     *     if (value != null)
+     *       map.put(key, value);
+     *     else
+     *       map.remove(key);
+     *   }
+     * }
+ * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. + */ + @SuppressWarnings("unchecked") public V merge + (K key, V value, BiFun remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalMerge(key, value, remappingFunction); + } + + /** + * Removes the key (and its corresponding value) from this map. + * This method does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V remove(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalReplace(key, null, null); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return false; + return internalReplace(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + return internalReplace(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V replace(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalReplace(key, value, null); + } + + /** + * Removes all of the mappings from this map. + */ + public void clear() { + internalClear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. + * + * @return the set view + */ + public KeySetView keySet() { + KeySetView ks = keySet; + return (ks != null) ? ks : (keySet = new KeySetView(this, null)); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll}). This is of + * course only appropriate if it is acceptable to use the same + * value for all additions from this view. + * + * @param mappedValue the mapped value to use for any + * additions. + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView(this, mappedValue); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. + */ + public ValuesView values() { + ValuesView vs = values; + return (vs != null) ? vs : (values = new ValuesView(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public Set> entrySet() { + EntrySetView es = entrySet; + return (es != null) ? es : (entrySet = new EntrySetView(this)); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the keys in this map. + * + * @return a partitionable iterator of the keys in this map + */ + public Spliterator keySpliterator() { + return new KeyIterator(this); + } + + /** + * Returns a partitionable iterator of the values in this map. + * + * @return a partitionable iterator of the values in this map + */ + public Spliterator valueSpliterator() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the entries in this map. + * + * @return a partitionable iterator of the entries in this map + */ + public Spliterator> entrySpliterator() { + return new EntryIterator(this); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + h += it.nextKey.hashCode() ^ v.hashCode(); + } + return h; + } + + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Traverser it = new Traverser(this); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Object v; + if ((v = it.advance()) != null) { + for (;;) { + Object k = it.nextKey; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((v = it.advance()) == null) + break; + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } + + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + Traverser it = new Traverser(this); + Object val; + while ((val = it.advance()) != null) { + Object v = m.get(it.nextKey); + if (v == null || (v != val && !v.equals(val))) + return false; + } + for (Map.Entry e : m.entrySet()) { + Object mk, mv, v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = internalGet(mk)) == null || + (mv != v && !mv.equals(v))) + return false; + } + } + return true; + } + + /* ----------------Iterators -------------- */ + + @SuppressWarnings("serial") static final class KeyIterator extends Traverser + implements Spliterator, Enumeration { + KeyIterator(ConcurrentHashMapV8 map) { super(map); } + KeyIterator(Traverser it) { + super(it); + } + public KeyIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new KeyIterator(this); + } + @SuppressWarnings("unchecked") public final K next() { + if (nextVal == null && advance() == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return (K) k; + } + + public final K nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class ValueIterator extends Traverser + implements Spliterator, Enumeration { + ValueIterator(ConcurrentHashMapV8 map) { super(map); } + ValueIterator(Traverser it) { + super(it); + } + public ValueIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new ValueIterator(this); + } + + @SuppressWarnings("unchecked") public final V next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + nextVal = null; + return (V) v; + } + + public final V nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class EntryIterator extends Traverser + implements Spliterator> { + EntryIterator(ConcurrentHashMapV8 map) { super(map); } + EntryIterator(Traverser it) { + super(it); + } + public EntryIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new EntryIterator(this); + } + + @SuppressWarnings("unchecked") public final Map.Entry next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return new MapEntry((K)k, (V)v, map); + } + } + + /** + * Exported Entry for iterators + */ + static final class MapEntry implements Map.Entry { + final K key; // non-null + V val; // non-null + final ConcurrentHashMapV8 map; + MapEntry(K key, V val, ConcurrentHashMapV8 map) { + this.key = key; + this.val = val; + this.map = map; + } + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString(){ return key + "=" + val; } + + public final boolean equals(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == val || v.equals(val))); + } + + /** + * Sets our entry's value and writes through to the map. The + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed in which case the put will + * re-establish). We do not and cannot guarantee more. + */ + public final V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = val; + val = value; + map.put(key, value); + return v; + } + } + + /* ---------------- Serialization Support -------------- */ + + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + Segment(float lf) { this.loadFactor = lf; } + } + + /** + * Saves the state of the {@code ConcurrentHashMapV8} instance to a + * stream (i.e., serializes it). + * @param s the stream + * @serialData + * the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + @SuppressWarnings("unchecked") private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + if (segments == null) { // for serialization compatibility + segments = (Segment[]) + new Segment[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) + segments[i] = new Segment(LOAD_FACTOR); + } + s.defaultWriteObject(); + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + s.writeObject(it.nextKey); + s.writeObject(v); + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * @param s the stream + */ + @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + this.segments = null; // unneeded + // initialize transient final field + this.counter = new LongAdder(); + + // Create all nodes, then place in table once size is known + long size = 0L; + Node p = null; + for (;;) { + K k = (K) s.readObject(); + V v = (V) s.readObject(); + if (k != null && v != null) { + int h = spread(k.hashCode()); + p = new Node(h, k, v, p); + ++size; + } + else + break; + } + if (p != null) { + boolean init = false; + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) + n = MAXIMUM_CAPACITY; + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + int sc = sizeCtl; + boolean collide = false; + if (n > sc && + SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == null) { + init = true; + AtomicReferenceArray tab = new AtomicReferenceArray(n); + int mask = n - 1; + while (p != null) { + int j = p.hash & mask; + Node next = p.next; + Node q = p.next = tabAt(tab, j); + setTabAt(tab, j, p); + if (!collide && q != null && q.hash == p.hash) + collide = true; + p = next; + } + table = tab; + counter.add(size); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + if (collide) { // rescan and convert to TreeBins + AtomicReferenceArray tab = table; + for (int i = 0; i < tab.length(); ++i) { + int c = 0; + for (Node e = tabAt(tab, i); e != null; e = e.next) { + if (++c > TREE_THRESHOLD && + (e.key instanceof Comparable)) { + replaceWithTreeBin(tab, i, e.key); + break; + } + } + } + } + } + if (!init) { // Can only happen if unsafely published. + while (p != null) { + internalPut(p.key, p.val); + p = p.next; + } + } + } + } + + + // ------------------------------------------------------- + + // Sams + /** Interface describing a void action of one argument */ + public interface Action { void apply(A a); } + /** Interface describing a void action of two arguments */ + public interface BiAction { void apply(A a, B b); } + /** Interface describing a function of one argument */ + public interface Generator { T apply(); } + /** Interface describing a function mapping its argument to a double */ + public interface ObjectToDouble { double apply(A a); } + /** Interface describing a function mapping its argument to a long */ + public interface ObjectToLong { long apply(A a); } + /** Interface describing a function mapping its argument to an int */ + public interface ObjectToInt {int apply(A a); } + /** Interface describing a function mapping two arguments to a double */ + public interface ObjectByObjectToDouble { double apply(A a, B b); } + /** Interface describing a function mapping two arguments to a long */ + public interface ObjectByObjectToLong { long apply(A a, B b); } + /** Interface describing a function mapping two arguments to an int */ + public interface ObjectByObjectToInt {int apply(A a, B b); } + /** Interface describing a function mapping a double to a double */ + public interface DoubleToDouble { double apply(double a); } + /** Interface describing a function mapping a long to a long */ + public interface LongToLong { long apply(long a); } + /** Interface describing a function mapping an int to an int */ + public interface IntToInt { int apply(int a); } + /** Interface describing a function mapping two doubles to a double */ + public interface DoubleByDoubleToDouble { double apply(double a, double b); } + /** Interface describing a function mapping two longs to a long */ + public interface LongByLongToLong { long apply(long a, long b); } + /** Interface describing a function mapping two ints to an int */ + public interface IntByIntToInt { int apply(int a, int b); } + + + /* ----------------Views -------------- */ + + /** + * Base class for views. + */ + static abstract class CHMView { + final ConcurrentHashMapV8 map; + CHMView(ConcurrentHashMapV8 map) { this.map = map; } + + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMapV8 getMap() { return map; } + + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + public final void clear() { map.clear(); } + + // implementations below rely on concrete classes supplying these + abstract public Iterator iterator(); + abstract public boolean contains(Object o); + abstract public boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = it.next(); + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") public final T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)it.next(); + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + public final int hashCode() { + int h = 0; + for (Iterator it = iterator(); it.hasNext();) + h += it.next().hashCode(); + return h; + } + + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); + } + + public final boolean containsAll(Collection c) { + if (c != this) { + for (Iterator it = c.iterator(); it.hasNext();) { + Object e = it.next(); + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. See + * {@link #keySet}, {@link #keySet(Object)}, {@link #newKeySet()}, + * {@link #newKeySet(int)}. + */ + public static class KeySetView extends CHMView implements Set, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMapV8 map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported. + */ + public V getMappedValue() { return value; } + + // implement Set API + + public boolean contains(Object o) { return map.containsKey(o); } + public boolean remove(Object o) { return map.remove(o) != null; } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the keys of this map + */ + public Iterator iterator() { return new KeyIterator(map); } + public boolean add(K e) { + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + if (e == null) + throw new NullPointerException(); + return map.internalPutIfAbsent(e, v) == null; + } + public boolean addAll(Collection c) { + boolean added = false; + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + for (K e : c) { + if (e == null) + throw new NullPointerException(); + if (map.internalPutIfAbsent(e, v) == null) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values}, + * + *

The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public static final class ValuesView extends CHMView + implements Collection { + ValuesView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { return map.containsValue(o); } + public final boolean remove(Object o) { + if (o != null) { + Iterator it = new ValueIterator(map); + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the values of this map + */ + public final Iterator iterator() { + return new ValueIterator(map); + } + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet}. + */ + public static final class EntrySetView extends CHMView + implements Set> { + EntrySetView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { + Object k, v, r; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); + } + public final boolean remove(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the entries of this map + */ + public final Iterator> iterator() { + return new EntryIterator(map); + } + + public final boolean add(Entry e) { + K key = e.getKey(); + V value = e.getValue(); + if (key == null || value == null) + throw new NullPointerException(); + return map.internalPut(key, value) == null; + } + public final boolean addAll(Collection> c) { + boolean added = false; + for (Entry e : c) { + if (add(e)) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } +} \ No newline at end of file diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java new file mode 100644 index 0000000..e7a1c5f --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java @@ -0,0 +1,204 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.9 version. + +package org.jruby.ext.thread_safe.jsr166e.nounsafe; + +import java.util.concurrent.atomic.AtomicLong; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectInputStream; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java new file mode 100644 index 0000000..ee69567 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java @@ -0,0 +1,291 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.5 version. + +package org.jruby.ext.thread_safe.jsr166e.nounsafe; + +import java.util.Random; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + + static AtomicLongFieldUpdater VALUE_UPDATER = AtomicLongFieldUpdater.newUpdater(Cell.class, "value"); + + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return VALUE_UPDATER.compareAndSet(this, cmp, val); + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + AtomicLongFieldUpdater BASE_UPDATER = AtomicLongFieldUpdater.newUpdater(Striped64.class, "base"); + AtomicIntegerFieldUpdater BUSY_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Striped64.class, "busy"); + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return BASE_UPDATER.compareAndSet(this, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return BUSY_UPDATER.compareAndSet(this, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } +} \ No newline at end of file diff --git a/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java new file mode 100644 index 0000000..81ba441 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java @@ -0,0 +1,199 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.16 version + +package org.jruby.ext.thread_safe.jsr166y; + +import java.util.Random; + +/** + * A random number generator isolated to the current thread. Like the + * global {@link java.util.Random} generator used by the {@link + * java.lang.Math} class, a {@code ThreadLocalRandom} is initialized + * with an internally generated seed that may not otherwise be + * modified. When applicable, use of {@code ThreadLocalRandom} rather + * than shared {@code Random} objects in concurrent programs will + * typically encounter much less overhead and contention. Use of + * {@code ThreadLocalRandom} is particularly appropriate when multiple + * tasks (for example, each a {@link ForkJoinTask}) use random numbers + * in parallel in thread pools. + * + *

Usages of this class should typically be of the form: + * {@code ThreadLocalRandom.current().nextX(...)} (where + * {@code X} is {@code Int}, {@code Long}, etc). + * When all usages are of this form, it is never possible to + * accidently share a {@code ThreadLocalRandom} across multiple threads. + * + *

This class also provides additional commonly used bounded random + * generation methods. + * + * @since 1.7 + * @author Doug Lea + */ +public class ThreadLocalRandom extends Random { + // same constants as Random, but must be redeclared because private + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + /** + * The random seed. We can't use super.seed. + */ + private long rnd; + + /** + * Initialization flag to permit calls to setSeed to succeed only + * while executing the Random constructor. We can't allow others + * since it would cause setting seed in one part of a program to + * unintentionally impact other usages by the thread. + */ + boolean initialized; + + // Padding to help avoid memory contention among seed updates in + // different TLRs in the common case that they are located near + // each other. + private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** + * The actual ThreadLocal + */ + private static final ThreadLocal localRandom = + new ThreadLocal() { + protected ThreadLocalRandom initialValue() { + return new ThreadLocalRandom(); + } + }; + + + /** + * Constructor called only by localRandom.initialValue. + */ + ThreadLocalRandom() { + super(); + initialized = true; + } + + /** + * Returns the current thread's {@code ThreadLocalRandom}. + * + * @return the current thread's {@code ThreadLocalRandom} + */ + public static ThreadLocalRandom current() { + return localRandom.get(); + } + + /** + * Throws {@code UnsupportedOperationException}. Setting seeds in + * this generator is not supported. + * + * @throws UnsupportedOperationException always + */ + public void setSeed(long seed) { + if (initialized) + throw new UnsupportedOperationException(); + rnd = (seed ^ multiplier) & mask; + } + + protected int next(int bits) { + rnd = (rnd * multiplier + addend) & mask; + return (int) (rnd >>> (48-bits)); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @throws IllegalArgumentException if least greater than or equal + * to bound + * @return the next value + */ + public int nextInt(int least, int bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextInt(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public long nextLong(long n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + // Divide n by two until small enough for nextInt. On each + // iteration (at most 31 of them but usually much less), + // randomly choose both whether to include high bit in result + // (offset) and whether to continue with the lower vs upper + // half (which makes a difference only if odd). + long offset = 0; + while (n >= Integer.MAX_VALUE) { + int bits = next(2); + long half = n >>> 1; + long nextn = ((bits & 2) == 0) ? half : n - half; + if ((bits & 1) == 0) + offset += n - nextn; + n = nextn; + } + return offset + nextInt((int) n); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public long nextLong(long least, long bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextLong(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code double} value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public double nextDouble(double n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + return nextDouble() * n; + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public double nextDouble(double least, double bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextDouble() * (bound - least) + least; + } + + private static final long serialVersionUID = -5851777807851030925L; +} diff --git a/.gems/gems/thread_safe-0.3.4/ext/thread_safe/JrubyCacheBackendService.java b/.gems/gems/thread_safe-0.3.4/ext/thread_safe/JrubyCacheBackendService.java new file mode 100644 index 0000000..2c5bcea --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/ext/thread_safe/JrubyCacheBackendService.java @@ -0,0 +1,15 @@ +package thread_safe; + +import java.io.IOException; + +import org.jruby.Ruby; +import org.jruby.ext.thread_safe.JRubyCacheBackendLibrary; +import org.jruby.runtime.load.BasicLibraryService; + +// can't name this JRubyCacheBackendService or else JRuby doesn't pick this up +public class JrubyCacheBackendService implements BasicLibraryService { + public boolean basicLoad(final Ruby runtime) throws IOException { + new JRubyCacheBackendLibrary().load(runtime, false); + return true; + } +} diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe.rb new file mode 100644 index 0000000..5b8fb27 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe.rb @@ -0,0 +1,65 @@ +require 'thread_safe/version' +require 'thread_safe/synchronized_delegator' + +module ThreadSafe + autoload :Cache, 'thread_safe/cache' + autoload :Util, 'thread_safe/util' + + # Various classes within allows for +nil+ values to be stored, so a special +NULL+ token is required to indicate the "nil-ness". + NULL = Object.new + + if defined?(JRUBY_VERSION) + require 'jruby/synchronized' + + # A thread-safe subclass of Array. This version locks + # against the object itself for every method call, + # ensuring only one thread can be reading or writing + # at a time. This includes iteration methods like + # #each. + class Array < ::Array + include JRuby::Synchronized + end + + # A thread-safe subclass of Hash. This version locks + # against the object itself for every method call, + # ensuring only one thread can be reading or writing + # at a time. This includes iteration methods like + # #each. + class Hash < ::Hash + include JRuby::Synchronized + end + elsif !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' + # Because MRI never runs code in parallel, the existing + # non-thread-safe structures should usually work fine. + Array = ::Array + Hash = ::Hash + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' + require 'monitor' + + class Hash < ::Hash; end + class Array < ::Array; end + + [Hash, Array].each do |klass| + klass.class_eval do + private + def _mon_initialize + @_monitor = Monitor.new unless @_monitor # avoid double initialisation + end + + def self.allocate + obj = super + obj.send(:_mon_initialize) + obj + end + end + + klass.superclass.instance_methods(false).each do |method| + klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{method}(*args) + @_monitor.synchronize { super } + end + RUBY_EVAL + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/atomic_reference_cache_backend.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/atomic_reference_cache_backend.rb new file mode 100644 index 0000000..0e4af2d --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/atomic_reference_cache_backend.rb @@ -0,0 +1,922 @@ +module ThreadSafe + # A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59 available in public domain. + # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59 + # + # The Ruby port skips out the +TreeBin+ (red-black trees for use in bins + # whose size exceeds a threshold). + # + # A hash table supporting full concurrency of retrievals and + # high expected concurrency for updates. However, even though all + # operations are thread-safe, retrieval operations do _not_ entail locking, + # and there is _not_ any support for locking the entire table + # in a way that prevents all access. + # + # Retrieval operations generally do not block, so may overlap with + # update operations. Retrievals reflect the results of the most + # recently _completed_ update operations holding upon their + # onset. (More formally, an update operation for a given key bears a + # _happens-before_ relation with any (non +nil+) retrieval for + # that key reporting the updated value.) For aggregate operations + # such as +clear()+, concurrent retrievals may reflect insertion or removal + # of only some entries. Similarly, the +each_pair+ iterator yields elements + # reflecting the state of the hash table at some point at or since + # the start of the +each_pair+. Bear in mind that the results of + # aggregate status methods including +size()+ and +empty?+} are typically + # useful only when a map is not undergoing concurrent updates in other + # threads. Otherwise the results of these methods reflect transient + # states that may be adequate for monitoring or estimation purposes, but not + # for program control. + # + # The table is dynamically expanded when there are too many + # collisions (i.e., keys that have distinct hash codes but fall into + # the same slot modulo the table size), with the expected average + # effect of maintaining roughly two bins per mapping (corresponding + # to a 0.75 load factor threshold for resizing). There may be much + # variance around this average as mappings are added and removed, but + # overall, this maintains a commonly accepted time/space tradeoff for + # hash tables. However, resizing this or any other kind of hash + # table may be a relatively slow operation. When possible, it is a + # good idea to provide a size estimate as an optional :initial_capacity + # initializer argument. An additional optional :load_factor constructor + # argument provides a further means of customizing initial table capacity + # by specifying the table density to be used in calculating the amount of + # space to allocate for the given number of elements. Note that using + # many keys with exactly the same +hash+ is a sure way to slow down + # performance of any hash table. + # + # == Design overview + # + # The primary design goal of this hash table is to maintain + # concurrent readability (typically method +[]+, but also + # iteration and related methods) while minimizing update + # contention. Secondary goals are to keep space consumption about + # the same or better than plain +Hash+, and to support high + # initial insertion rates on an empty table by many threads. + # + # Each key-value mapping is held in a +Node+. The validation-based + # approach explained below leads to a lot of code sprawl because + # retry-control precludes factoring into smaller methods. + # + # The table is lazily initialized to a power-of-two size upon the + # first insertion. Each bin in the table normally contains a + # list of +Node+s (most often, the list has only zero or one +Node+). + # Table accesses require volatile/atomic reads, writes, and + # CASes. The lists of nodes within bins are always accurately traversable + # under volatile reads, so long as lookups check hash code + # and non-nullness of value before checking key equality. + # + # We use the top two bits of +Node+ hash fields for control + # purposes -- they are available anyway because of addressing + # constraints. As explained further below, these top bits are + # used as follows: + # 00 - Normal + # 01 - Locked + # 11 - Locked and may have a thread waiting for lock + # 10 - +Node+ is a forwarding node + # + # The lower 28 bits of each +Node+'s hash field contain a + # the key's hash code, except for forwarding nodes, for which + # the lower bits are zero (and so always have hash field == +MOVED+). + # + # Insertion (via +[]=+ or its variants) of the first node in an + # empty bin is performed by just CASing it to the bin. This is + # by far the most common case for put operations under most + # key/hash distributions. Other update operations (insert, + # delete, and replace) require locks. We do not want to waste + # the space required to associate a distinct lock object with + # each bin, so instead use the first node of a bin list itself as + # a lock. Blocking support for these locks relies +Util::CheapLockable. + # However, we also need a +try_lock+ construction, so we overlay + # these by using bits of the +Node+ hash field for lock control (see above), + # and so normally use builtin monitors only for blocking and signalling using + # +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+. + # + # Using the first node of a list as a lock does not by itself + # suffice though: When a node is locked, any update must first + # validate that it is still the first node after locking it, and + # retry if not. Because new nodes are always appended to lists, + # once a node is first in a bin, it remains first until deleted + # or the bin becomes invalidated (upon resizing). However, + # operations that only conditionally update may inspect nodes + # until the point of update. This is a converse of sorts to the + # lazy locking technique described by Herlihy & Shavit. + # + # The main disadvantage of per-bin locks is that other update + # operations on other nodes in a bin list protected by the same + # lock can stall, for example when user +eql?+ or mapping + # functions take a long time. However, statistically, under + # random hash codes, this is not a common problem. Ideally, the + # frequency of nodes in bins follows a Poisson distribution + # (http://en.wikipedia.org/wiki/Poisson_distribution) with a + # parameter of about 0.5 on average, given the resizing threshold + # of 0.75, although with a large variance because of resizing + # granularity. Ignoring variance, the expected occurrences of + # list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + # first values are: + # + # 0: 0.60653066 + # 1: 0.30326533 + # 2: 0.07581633 + # 3: 0.01263606 + # 4: 0.00157952 + # 5: 0.00015795 + # 6: 0.00001316 + # 7: 0.00000094 + # 8: 0.00000006 + # more: less than 1 in ten million + # + # Lock contention probability for two threads accessing distinct + # elements is roughly 1 / (8 * #elements) under random hashes. + # + # The table is resized when occupancy exceeds a percentage + # threshold (nominally, 0.75, but see below). Only a single + # thread performs the resize (using field +size_control+, to arrange + # exclusion), but the table otherwise remains usable for reads + # and updates. Resizing proceeds by transferring bins, one by + # one, from the table to the next table. Because we are using + # power-of-two expansion, the elements from each bin must either + # stay at same index, or move with a power of two offset. We + # eliminate unnecessary node creation by catching cases where old + # nodes can be reused because their next fields won't change. On + # average, only about one-sixth of them need cloning when a table + # doubles. The nodes they replace will be garbage collectable as + # soon as they are no longer referenced by any reader thread that + # may be in the midst of concurrently traversing table. Upon + # transfer, the old table bin contains only a special forwarding + # node (with hash field +MOVED+) that contains the next table as + # its key. On encountering a forwarding node, access and update + # operations restart, using the new table. + # + # Each bin transfer requires its bin lock. However, unlike other + # cases, a transfer can skip a bin if it fails to acquire its + # lock, and revisit it later. Method +rebuild+ maintains a buffer of + # TRANSFER_BUFFER_SIZE bins that have been skipped because of failure + # to acquire a lock, and blocks only if none are available + # (i.e., only very rarely). The transfer operation must also ensure + # that all accessible bins in both the old and new table are usable by + # any traversal. When there are no lock acquisition failures, this is + # arranged simply by proceeding from the last bin (+table.size - 1+) up + # towards the first. Upon seeing a forwarding node, traversals arrange + # to move to the new table without revisiting nodes. However, when any + # node is skipped during a transfer, all earlier table bins may have + # become visible, so are initialized with a reverse-forwarding node back + # to the old table until the new ones are established. (This + # sometimes requires transiently locking a forwarding node, which + # is possible under the above encoding.) These more expensive + # mechanics trigger only when necessary. + # + # The traversal scheme also applies to partial traversals of + # ranges of bins (via an alternate Traverser constructor) + # to support partitioned aggregate operations. Also, read-only + # operations give up if ever forwarded to a null table, which + # provides support for shutdown-style clearing, which is also not + # currently implemented. + # + # Lazy table initialization minimizes footprint until first use. + # + # The element count is maintained using a +ThreadSafe::Util::Adder+, + # which avoids contention on updates but can encounter cache thrashing + # if read too frequently during concurrent access. To avoid reading so + # often, resizing is attempted either when a bin lock is + # contended, or upon adding to a bin already holding two or more + # nodes (checked before adding in the +x_if_absent+ methods, after + # adding in others). Under uniform hash distributions, the + # probability of this occurring at threshold is around 13%, + # meaning that only about 1 in 8 puts check threshold (and after + # resizing, many fewer do so). But this approximation has high + # variance for small table sizes, so we check on any collision + # for sizes <= 64. The bulk putAll operation further reduces + # contention by only committing count updates upon these size + # checks. + class AtomicReferenceCacheBackend + class Table < Util::PowerOfTwoTuple + def cas_new_node(i, hash, key, value) + cas(i, nil, Node.new(hash, key, value)) + end + + def try_to_cas_in_computed(i, hash, key) + succeeded = false + new_value = nil + new_node = Node.new(locked_hash = hash | LOCKED, key, NULL) + if cas(i, nil, new_node) + begin + if NULL == (new_value = yield(NULL)) + was_null = true + else + new_node.value = new_value + end + succeeded = true + ensure + volatile_set(i, nil) if !succeeded || was_null + new_node.unlock_via_hash(locked_hash, hash) + end + end + return succeeded, new_value + end + + def try_lock_via_hash(i, node, node_hash) + node.try_lock_via_hash(node_hash) do + yield if volatile_get(i) == node + end + end + + def delete_node_at(i, node, predecessor_node) + if predecessor_node + predecessor_node.next = node.next + else + volatile_set(i, node.next) + end + end + end + + # Key-value entry. Nodes with a hash field of +MOVED+ are special, + # and do not contain user keys or values. Otherwise, keys are never +nil+, + # and +NULL+ +value+ fields indicate that a node is in the process + # of being deleted or created. For purposes of read-only access, a key may be read + # before a value, but can only be used after checking value to be +!= NULL+. + class Node + extend Util::Volatile + attr_volatile :hash, :value, :next + + include Util::CheapLockable + + bit_shift = Util::FIXNUM_BIT_SIZE - 2 # need 2 bits for ourselves + # Encodings for special uses of Node hash fields. See above for explanation. + MOVED = ('10' << ('0' * bit_shift)).to_i(2) # hash field for forwarding nodes + LOCKED = ('01' << ('0' * bit_shift)).to_i(2) # set/tested only as a bit + WAITING = ('11' << ('0' * bit_shift)).to_i(2) # both bits set/tested together + HASH_BITS = ('00' << ('1' * bit_shift)).to_i(2) # usable bits of normal node hash + + SPIN_LOCK_ATTEMPTS = Util::CPU_COUNT > 1 ? Util::CPU_COUNT * 2 : 0 + + attr_reader :key + + def initialize(hash, key, value, next_node = nil) + super() + @key = key + self.lazy_set_hash(hash) + self.lazy_set_value(value) + self.next = next_node + end + + # Spins a while if +LOCKED+ bit set and this node is the first + # of its bin, and then sets +WAITING+ bits on hash field and + # blocks (once) if they are still set. It is OK for this + # method to return even if lock is not available upon exit, + # which enables these simple single-wait mechanics. + # + # The corresponding signalling operation is performed within + # callers: Upon detecting that +WAITING+ has been set when + # unlocking lock (via a failed CAS from non-waiting +LOCKED+ + # state), unlockers acquire the +cheap_synchronize+ lock and + # perform a +cheap_broadcast+. + def try_await_lock(table, i) + if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking? + spins = SPIN_LOCK_ATTEMPTS + randomizer = base_randomizer = Util::XorShiftRandom.get + while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash) + if spins >= 0 + if (randomizer = (randomizer >> 1)).even? # spin at random + if (spins -= 1) == 0 + Thread.pass # yield before blocking + else + randomizer = base_randomizer = Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? + end + end + elsif cas_hash(my_hash, my_hash | WAITING) + force_aquire_lock(table, i) + break + end + end + end + end + + def key?(key) + @key.eql?(key) + end + + def matches?(key, hash) + pure_hash == hash && key?(key) + end + + def pure_hash + hash & HASH_BITS + end + + def try_lock_via_hash(node_hash = hash) + if cas_hash(node_hash, locked_hash = node_hash | LOCKED) + begin + yield + ensure + unlock_via_hash(locked_hash, node_hash) + end + end + end + + def locked? + self.class.locked_hash?(hash) + end + + def unlock_via_hash(locked_hash, node_hash) + unless cas_hash(locked_hash, node_hash) + self.hash = node_hash + cheap_synchronize { cheap_broadcast } + end + end + + private + def force_aquire_lock(table, i) + cheap_synchronize do + if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING + cheap_wait + else + cheap_broadcast # possibly won race vs signaller + end + end + end + + class << self + def locked_hash?(hash) + (hash & LOCKED) != 0 + end + end + end + + # shorthands + MOVED = Node::MOVED + LOCKED = Node::LOCKED + WAITING = Node::WAITING + HASH_BITS = Node::HASH_BITS + + NOW_RESIZING = -1 + DEFAULT_CAPACITY = 16 + MAX_CAPACITY = Util::MAX_INT + + # The buffer size for skipped bins during transfers. The + # value is arbitrary but should be large enough to avoid + # most locking stalls during resizes. + TRANSFER_BUFFER_SIZE = 32 + + extend Util::Volatile + attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two. + + # Table initialization and resizing control. When negative, the + # table is being initialized or resized. Otherwise, when table is + # null, holds the initial table size to use upon creation, or 0 + # for default. After initialization, holds the next element count + # value upon which to resize the table. + :size_control + + def initialize(options = nil) + super() + @counter = Util::Adder.new + initial_capacity = options && options[:initial_capacity] || DEFAULT_CAPACITY + self.size_control = (capacity = table_size_for(initial_capacity)) > MAX_CAPACITY ? MAX_CAPACITY : capacity + end + + def get_or_default(key, else_value = nil) + hash = key_hash(key) + current_table = table + while current_table + node = current_table.volatile_get_by_hash(hash) + current_table = + while node + if (node_hash = node.hash) == MOVED + break node.key + elsif (node_hash & HASH_BITS) == hash && node.key?(key) && NULL != (value = node.value) + return value + end + node = node.next + end + end + else_value + end + + def [](key) + get_or_default(key) + end + + def key?(key) + get_or_default(key, NULL) != NULL + end + + def []=(key, value) + get_and_set(key, value) + value + end + + def compute_if_absent(key) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key) { yield } + if succeeded + increment_size + return new_value + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif NULL != (current_value = find_value_in_node_list(node, key, hash, node_hash & HASH_BITS)) + return current_value + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, value = attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) { yield } + return value if succeeded + end + end + end + + def compute_if_present(key) + new_value = nil + internal_replace(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + new_value + end + + def compute(key) + internal_compute(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + end + + def merge_pair(key, value) + internal_compute(key) do |old_value| + if NULL == old_value || !(value = yield(old_value)).nil? + value + else + NULL + end + end + end + + def replace_pair(key, old_value, new_value) + NULL != internal_replace(key, old_value) { new_value } + end + + def replace_if_exists(key, new_value) + if (result = internal_replace(key) { new_value }) && NULL != result + result + end + end + + def get_and_set(key, value) # internalPut in the original CHMV8 + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + if current_table.cas_new_node(i, hash, key, value) + increment_size + break + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + break old_value if succeeded + end + end + end + + def delete(key) + replace_if_exists(key, NULL) + end + + def delete_pair(key, value) + result = internal_replace(key, value) { NULL } + if result && NULL != result + !!result + else + false + end + end + + def each_pair + return self unless current_table = table + current_table_size = base_size = current_table.size + i = base_index = 0 + while base_index < base_size + if node = current_table.volatile_get(i) + if node.hash == MOVED + current_table = node.key + current_table_size = current_table.size + else + begin + if NULL != (value = node.value) # skip deleted or special nodes + yield node.key, value + end + end while node = node.next + end + end + + if (i_with_base = i + base_size) < current_table_size + i = i_with_base # visit upper slots if present + else + i = base_index += 1 + end + end + self + end + + def size + (sum = @counter.sum) < 0 ? 0 : sum # ignore transient negative values + end + + def empty? + size == 0 + end + + # Implementation for clear. Steps through each bin, removing all nodes. + def clear + return self unless current_table = table + current_table_size = current_table.size + deleted_count = i = 0 + while i < current_table_size + if !(node = current_table.volatile_get(i)) + i += 1 + elsif (node_hash = node.hash) == MOVED + current_table = node.key + current_table_size = current_table.size + elsif Node.locked_hash?(node_hash) + decrement_size(deleted_count) # opportunistically update count + deleted_count = 0 + node.try_await_lock(current_table, i) + else + current_table.try_lock_via_hash(i, node, node_hash) do + begin + deleted_count += 1 if NULL != node.value # recheck under lock + node.value = nil + end while node = node.next + current_table.volatile_set(i, nil) + i += 1 + end + end + end + decrement_size(deleted_count) + self + end + + private + # Internal versions of the insertion methods, each a + # little more complicated than the last. All have + # the same basic structure: + # 1. If table uninitialized, create + # 2. If bin empty, try to CAS new node + # 3. If bin stale, use new table + # 4. Lock and validate; if valid, scan and add or update + # + # The others interweave other checks and/or alternative actions: + # * Plain +get_and_set+ checks for and performs resize after insertion. + # * compute_if_absent prescans for mapping without lock (and fails to add + # if present), which also makes pre-emptive resize checks worthwhile. + # + # Someday when details settle down a bit more, it might be worth + # some factoring to reduce sprawl. + def internal_replace(key, expected_old_value = NULL, &block) + hash = key_hash(key) + current_table = table + while current_table + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + break + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif (node_hash & HASH_BITS) != hash && !node.next # precheck + break # rules out possible existence + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash, &block) + return old_value if succeeded + end + end + NULL + end + + def attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash) + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + old_value = NULL + begin + if node.matches?(key, hash) && NULL != (current_value = node.value) + if NULL == expected_old_value || expected_old_value == current_value # NULL == expected_old_value means whatever value + old_value = current_value + if NULL == (node.value = yield(old_value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + end + end + break + end + + predecessor_node = node + end while node = node.next + + return true, old_value + end + end + + def find_value_in_node_list(node, key, hash, pure_hash) + do_check_for_resize = false + while true + if pure_hash == hash && node.key?(key) && NULL != (value = node.value) + return value + elsif node = node.next + do_check_for_resize = true # at least 2 nodes -> check for resize + pure_hash = node.pure_hash + else + return NULL + end + end + ensure + check_for_resize if do_check_for_resize + end + + def internal_compute(key, &block) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key, &block) + if succeeded + if NULL == new_value + break nil + else + increment_size + break new_value + end + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, new_value = attempt_compute(key, hash, current_table, i, node, node_hash, &block) + break new_value if succeeded + end + end + end + + def attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + while true + if node.matches?(key, hash) && NULL != (value = node.value) + return true, value + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value = yield) + added = true + increment_size + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_compute(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + while true + if node.matches?(key, hash) && NULL != (value = node.value) + if NULL == (node.value = value = yield(value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + value = nil + end + return true, value + end + predecessor_node = node + unless node = node.next + if NULL == (value = yield(NULL)) + value = nil + else + predecessor_node.next = Node.new(hash, key, value) + added = true + increment_size + end + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + node_nesting = nil + current_table.try_lock_via_hash(i, node, node_hash) do + node_nesting = 1 + old_value = nil + found_old_value = false + while node + if node.matches?(key, hash) && NULL != (old_value = node.value) + found_old_value = true + node.value = value + break + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value) + break + end + node_nesting += 1 + end + + return true, old_value if found_old_value + increment_size + true + end + ensure + check_for_resize if node_nesting && (node_nesting > 1 || current_table.size <= 64) + end + + def initialize_copy(other) + super + @counter = Util::Adder.new + self.table = nil + self.size_control = (other_table = other.table) ? other_table.size : DEFAULT_CAPACITY + self + end + + def try_await_lock(current_table, i, node) + check_for_resize # try resizing if can't get lock + node.try_await_lock(current_table, i) + end + + def key_hash(key) + key.hash & HASH_BITS + end + + # Returns a power of two table size for the given desired capacity. + def table_size_for(entry_count) + size = 2 + size <<= 1 while size < entry_count + size + end + + # Initializes table, using the size recorded in +size_control+. + def initialize_table + until current_table ||= table + if (size_ctrl = size_control) == NOW_RESIZING + Thread.pass # lost initialization race; just spin + else + try_in_resize_lock(current_table, size_ctrl) do + initial_size = size_ctrl > 0 ? size_ctrl : DEFAULT_CAPACITY + current_table = self.table = Table.new(initial_size) + initial_size - (initial_size >> 2) # 75% load factor + end + end + end + current_table + end + + # If table is too small and not already resizing, creates next + # table and transfers bins. Rechecks occupancy after a transfer + # to see if another resize is already needed because resizings + # are lagging additions. + def check_for_resize + while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum + try_in_resize_lock(current_table, size_ctrl) do + self.table = rebuild(current_table) + (table_size << 1) - (table_size >> 1) # 75% load factor + end + end + end + + def try_in_resize_lock(current_table, size_ctrl) + if cas_size_control(size_ctrl, NOW_RESIZING) + begin + if current_table == table # recheck under lock + size_ctrl = yield # get new size_control + end + ensure + self.size_control = size_ctrl + end + end + end + + # Moves and/or copies the nodes in each bin to new table. See above for explanation. + def rebuild(table) + old_table_size = table.size + new_table = table.next_in_size_table + # puts "#{old_table_size} -> #{new_table.size}" + forwarder = Node.new(MOVED, new_table, NULL) + rev_forwarder = nil + locked_indexes = nil # holds bins to revisit; nil until needed + locked_arr_idx = 0 + bin = old_table_size - 1 + i = bin + while true + if !(node = table.volatile_get(i)) + # no lock needed (or available) if bin >= 0, because we're not popping values from locked_indexes until we've run through the whole table + redo unless (bin >= 0 ? table.cas(i, nil, forwarder) : lock_and_clean_up_reverse_forwarders(table, old_table_size, new_table, i, forwarder)) + elsif Node.locked_hash?(node_hash = node.hash) + locked_indexes ||= Array.new + if bin < 0 && locked_arr_idx > 0 + locked_arr_idx -= 1 + i, locked_indexes[locked_arr_idx] = locked_indexes[locked_arr_idx], i # swap with another bin + redo + end + if bin < 0 || locked_indexes.size >= TRANSFER_BUFFER_SIZE + node.try_await_lock(table, i) # no other options -- block + redo + end + rev_forwarder ||= Node.new(MOVED, table, NULL) + redo unless table.volatile_get(i) == node && node.locked? # recheck before adding to list + locked_indexes << i + new_table.volatile_set(i, rev_forwarder) + new_table.volatile_set(i + old_table_size, rev_forwarder) + else + redo unless split_old_bin(table, new_table, i, node, node_hash, forwarder) + end + + if bin > 0 + i = (bin -= 1) + elsif locked_indexes && !locked_indexes.empty? + bin = -1 + i = locked_indexes.pop + locked_arr_idx = locked_indexes.size - 1 + else + return new_table + end + end + end + + def lock_and_clean_up_reverse_forwarders(old_table, old_table_size, new_table, i, forwarder) + # transiently use a locked forwarding node + locked_forwarder = Node.new(moved_locked_hash = MOVED | LOCKED, new_table, NULL) + if old_table.cas(i, nil, locked_forwarder) + new_table.volatile_set(i, nil) # kill the potential reverse forwarders + new_table.volatile_set(i + old_table_size, nil) # kill the potential reverse forwarders + old_table.volatile_set(i, forwarder) + locked_forwarder.unlock_via_hash(moved_locked_hash, MOVED) + true + end + end + + # Splits a normal bin with list headed by e into lo and hi parts; installs in given table. + def split_old_bin(table, new_table, i, node, node_hash, forwarder) + table.try_lock_via_hash(i, node, node_hash) do + split_bin(new_table, i, node, node_hash) + table.volatile_set(i, forwarder) + end + end + + def split_bin(new_table, i, node, node_hash) + bit = new_table.size >> 1 # bit to split on + run_bit = node_hash & bit + last_run = nil + low = nil + high = nil + current_node = node + # this optimises for the lowest amount of volatile writes and objects created + while current_node = current_node.next + unless (b = current_node.hash & bit) == run_bit + run_bit = b + last_run = current_node + end + end + if run_bit == 0 + low = last_run + else + high = last_run + end + current_node = node + until current_node == last_run + pure_hash = current_node.pure_hash + if (pure_hash & bit) == 0 + low = Node.new(pure_hash, current_node.key, current_node.value, low) + else + high = Node.new(pure_hash, current_node.key, current_node.value, high) + end + current_node = current_node.next + end + new_table.volatile_set(i, low) + new_table.volatile_set(i + bit, high) + end + + def increment_size + @counter.increment + end + + def decrement_size(by = 1) + @counter.add(-by) + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/cache.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/cache.rb new file mode 100644 index 0000000..2539941 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/cache.rb @@ -0,0 +1,163 @@ +require 'thread' + +module ThreadSafe + autoload :JRubyCacheBackend, 'thread_safe/jruby_cache_backend' + autoload :MriCacheBackend, 'thread_safe/mri_cache_backend' + autoload :NonConcurrentCacheBackend, 'thread_safe/non_concurrent_cache_backend' + autoload :AtomicReferenceCacheBackend, 'thread_safe/atomic_reference_cache_backend' + autoload :SynchronizedCacheBackend, 'thread_safe/synchronized_cache_backend' + + ConcurrentCacheBackend = if defined?(RUBY_ENGINE) + case RUBY_ENGINE + when 'jruby'; JRubyCacheBackend + when 'ruby'; MriCacheBackend + when 'rbx'; AtomicReferenceCacheBackend + else + warn 'ThreadSafe: unsupported Ruby engine, using a fully synchronized ThreadSafe::Cache implementation' if $VERBOSE + SynchronizedCacheBackend + end + else + MriCacheBackend + end + + class Cache < ConcurrentCacheBackend + KEY_ERROR = defined?(KeyError) ? KeyError : IndexError # there is no KeyError in 1.8 mode + + def initialize(options = nil, &block) + if options.kind_of?(::Hash) + validate_options_hash!(options) + else + options = nil + end + + super(options) + @default_proc = block + end + + def [](key) + if value = super # non-falsy value is an existing mapping, return it right away + value + # re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call + # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value + # would be returned) + # note: nil == value check is not technically necessary + elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL)) + @default_proc.call(self, key) + else + value + end + end + + alias_method :get, :[] + alias_method :put, :[]= + + def fetch(key, default_value = NULL) + if NULL != (value = get_or_default(key, NULL)) + value + elsif block_given? + yield key + elsif NULL != default_value + default_value + else + raise_fetch_no_key + end + end + + def fetch_or_store(key, default_value = NULL) + fetch(key) do + put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value)) + end + end + + def put_if_absent(key, value) + computed = false + result = compute_if_absent(key) do + computed = true + value + end + computed ? nil : result + end unless method_defined?(:put_if_absent) + + def value?(value) + each_value do |v| + return true if value.equal?(v) + end + false + end unless method_defined?(:value?) + + def keys + arr = [] + each_pair {|k, v| arr << k} + arr + end unless method_defined?(:keys) + + def values + arr = [] + each_pair {|k, v| arr << v} + arr + end unless method_defined?(:values) + + def each_key + each_pair {|k, v| yield k} + end unless method_defined?(:each_key) + + def each_value + each_pair {|k, v| yield v} + end unless method_defined?(:each_value) + + def key(value) + each_pair {|k, v| return k if v == value} + nil + end unless method_defined?(:key) + alias_method :index, :key if RUBY_VERSION < '1.9' + + def empty? + each_pair {|k, v| return false} + true + end unless method_defined?(:empty?) + + def size + count = 0 + each_pair {|k, v| count += 1} + count + end unless method_defined?(:size) + + def marshal_dump + raise TypeError, "can't dump hash with default proc" if @default_proc + h = {} + each_pair {|k, v| h[k] = v} + h + end + + def marshal_load(hash) + initialize + populate_from(hash) + end + + undef :freeze + + private + def raise_fetch_no_key + raise KEY_ERROR, 'key not found' + end + + def initialize_copy(other) + super + populate_from(other) + end + + def populate_from(hash) + hash.each_pair {|k, v| self[k] = v} + self + end + + def validate_options_hash!(options) + if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Fixnum) || initial_capacity < 0) + raise ArgumentError, ":initial_capacity must be a positive Fixnum" + end + if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1) + raise ArgumentError, ":load_factor must be a number between 0 and 1" + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/mri_cache_backend.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/mri_cache_backend.rb new file mode 100644 index 0000000..8768297 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/mri_cache_backend.rb @@ -0,0 +1,62 @@ +module ThreadSafe + class MriCacheBackend < NonConcurrentCacheBackend + # We can get away with a single global write lock (instead of a per-instance one) because of the GVL/green threads. + # + # The previous implementation used `Thread.critical` on 1.8 MRI to implement the 4 composed atomic operations (`put_if_absent`, `replace_pair`, + # `replace_if_exists`, `delete_pair`) this however doesn't work for `compute_if_absent` because on 1.8 the Mutex class is itself implemented + # via `Thread.critical` and a call to `Mutex#lock` does not restore the previous `Thread.critical` value (thus any synchronisation clears the + # `Thread.critical` flag and we loose control). This poses a problem as the provided block might use synchronisation on its own. + # + # NOTE: a neat idea of writing a c-ext to manually perform atomic put_if_absent, while relying on Ruby not releasing a GVL while calling + # a c-ext will not work because of the potentially Ruby implemented `#hash` and `#eql?` key methods. + WRITE_LOCK = Mutex.new + + def []=(key, value) + WRITE_LOCK.synchronize { super } + end + + def compute_if_absent(key) + if stored_value = _get(key) # fast non-blocking path for the most likely case + stored_value + else + WRITE_LOCK.synchronize { super } + end + end + + def compute_if_present(key) + WRITE_LOCK.synchronize { super } + end + + def compute(key) + WRITE_LOCK.synchronize { super } + end + + def merge_pair(key, value) + WRITE_LOCK.synchronize { super } + end + + def replace_pair(key, old_value, new_value) + WRITE_LOCK.synchronize { super } + end + + def replace_if_exists(key, new_value) + WRITE_LOCK.synchronize { super } + end + + def get_and_set(key, value) + WRITE_LOCK.synchronize { super } + end + + def delete(key) + WRITE_LOCK.synchronize { super } + end + + def delete_pair(key, value) + WRITE_LOCK.synchronize { super } + end + + def clear + WRITE_LOCK.synchronize { super } + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/non_concurrent_cache_backend.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/non_concurrent_cache_backend.rb new file mode 100644 index 0000000..2ef86d8 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/non_concurrent_cache_backend.rb @@ -0,0 +1,133 @@ +module ThreadSafe + class NonConcurrentCacheBackend + # WARNING: all public methods of the class must operate on the @backend directly without calling each other. This is important + # because of the SynchronizedCacheBackend which uses a non-reentrant mutex for perfomance reasons. + def initialize(options = nil) + @backend = {} + end + + def [](key) + @backend[key] + end + + def []=(key, value) + @backend[key] = value + end + + def compute_if_absent(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + stored_value + else + @backend[key] = yield + end + end + + def replace_pair(key, old_value, new_value) + if pair?(key, old_value) + @backend[key] = new_value + true + else + false + end + end + + def replace_if_exists(key, new_value) + if NULL != (stored_value = @backend.fetch(key, NULL)) + @backend[key] = new_value + stored_value + end + end + + def compute_if_present(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + store_computed_value(key, yield(stored_value)) + end + end + + def compute(key) + store_computed_value(key, yield(@backend[key])) + end + + def merge_pair(key, value) + if NULL == (stored_value = @backend.fetch(key, NULL)) + @backend[key] = value + else + store_computed_value(key, yield(stored_value)) + end + end + + def get_and_set(key, value) + stored_value = @backend[key] + @backend[key] = value + stored_value + end + + def key?(key) + @backend.key?(key) + end + + def value?(value) + @backend.value?(value) + end + + def delete(key) + @backend.delete(key) + end + + def delete_pair(key, value) + if pair?(key, value) + @backend.delete(key) + true + else + false + end + end + + def clear + @backend.clear + self + end + + def each_pair + dupped_backend.each_pair do |k, v| + yield k, v + end + self + end + + def size + @backend.size + end + + def get_or_default(key, default_value) + @backend.fetch(key, default_value) + end + + alias_method :_get, :[] + alias_method :_set, :[]= + private :_get, :_set + private + def initialize_copy(other) + super + @backend = {} + self + end + + def dupped_backend + @backend.dup + end + + def pair?(key, expected_value) + NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value) + end + + def store_computed_value(key, new_value) + if new_value.nil? + @backend.delete(key) + nil + else + @backend[key] = new_value + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_cache_backend.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_cache_backend.rb new file mode 100644 index 0000000..37a897f --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_cache_backend.rb @@ -0,0 +1,76 @@ +module ThreadSafe + class SynchronizedCacheBackend < NonConcurrentCacheBackend + require 'mutex_m' + include Mutex_m + # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are not allowed to call each other. + + def [](key) + synchronize { super } + end + + def []=(key, value) + synchronize { super } + end + + def compute_if_absent(key) + synchronize { super } + end + + def compute_if_present(key) + synchronize { super } + end + + def compute(key) + synchronize { super } + end + + def merge_pair(key, value) + synchronize { super } + end + + def replace_pair(key, old_value, new_value) + synchronize { super } + end + + def replace_if_exists(key, new_value) + synchronize { super } + end + + def get_and_set(key, value) + synchronize { super } + end + + def key?(key) + synchronize { super } + end + + def value?(value) + synchronize { super } + end + + def delete(key) + synchronize { super } + end + + def delete_pair(key, value) + synchronize { super } + end + + def clear + synchronize { super } + end + + def size + synchronize { super } + end + + def get_or_default(key, default_value) + synchronize { super } + end + + private + def dupped_backend + synchronize { super } + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_delegator.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_delegator.rb new file mode 100644 index 0000000..9e18a56 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/synchronized_delegator.rb @@ -0,0 +1,60 @@ +require 'delegate' +require 'monitor' + +# This class provides a trivial way to synchronize all calls to a given object +# by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls +# around the delegated `#send`. Example: +# +# array = [] # not thread-safe on many impls +# array = SynchronizedDelegator.new([]) # thread-safe +# +# A simple `Monitor` provides a very coarse-grained way to synchronize a given +# object, in that it will cause synchronization for methods that have no +# need for it, but this is a trivial way to get thread-safety where none may +# exist currently on some implementations. +# +# This class is currently being considered for inclusion into stdlib, via +# https://bugs.ruby-lang.org/issues/8556 +class SynchronizedDelegator < SimpleDelegator + def setup + @old_abort = Thread.abort_on_exception + Thread.abort_on_exception = true + end + + def teardown + Thread.abort_on_exception = @old_abort + end + + def initialize(obj) + __setobj__(obj) + @monitor = Monitor.new + end + + def method_missing(method, *args, &block) + monitor = @monitor + begin + monitor.enter + super + ensure + monitor.exit + end + end + + # Work-around for 1.8 std-lib not passing block around to delegate. + # @private + def method_missing(method, *args, &block) + monitor = @monitor + begin + monitor.enter + target = self.__getobj__ + if target.respond_to?(method) + target.__send__(method, *args, &block) + else + super(method, *args, &block) + end + ensure + monitor.exit + end + end if RUBY_VERSION[0, 3] == '1.8' + +end unless defined?(SynchronizedDelegator) diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util.rb new file mode 100644 index 0000000..bad4238 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util.rb @@ -0,0 +1,16 @@ +module ThreadSafe + module Util + FIXNUM_BIT_SIZE = (0.size * 8) - 2 + MAX_INT = (2 ** FIXNUM_BIT_SIZE) - 1 + CPU_COUNT = 16 # is there a way to determine this? + + autoload :AtomicReference, 'thread_safe/util/atomic_reference' + autoload :Adder, 'thread_safe/util/adder' + autoload :CheapLockable, 'thread_safe/util/cheap_lockable' + autoload :PowerOfTwoTuple, 'thread_safe/util/power_of_two_tuple' + autoload :Striped64, 'thread_safe/util/striped64' + autoload :Volatile, 'thread_safe/util/volatile' + autoload :VolatileTuple, 'thread_safe/util/volatile_tuple' + autoload :XorShiftRandom, 'thread_safe/util/xor_shift_random' + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/adder.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/adder.rb new file mode 100644 index 0000000..35d01ee --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/adder.rb @@ -0,0 +1,59 @@ +module ThreadSafe + module Util + # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8 available in public domain. + # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8 + # + # One or more variables that together maintain an initially zero + # sum. When updates (method +add+) are contended across threads, + # the set of variables may grow dynamically to reduce contention. + # Method +sum+ returns the current total combined across the + # variables maintaining the sum. + # + # This class is usually preferable to single +Atomic+ reference when + # multiple threads update a common sum that is used for purposes such + # as collecting statistics, not for fine-grained synchronization + # control. Under low update contention, the two classes have similar + # characteristics. But under high contention, expected throughput of + # this class is significantly higher, at the expense of higher space + # consumption. + class Adder < Striped64 + # Adds the given value. + def add(x) + if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x} + was_uncontended = true + hash = hash_code + unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x}) + retry_update(x, hash, was_uncontended) {|current_value| current_value + x} + end + end + end + + def increment + add(1) + end + + def decrement + add(-1) + end + + # Returns the current sum. The returned value is _NOT_ an + # atomic snapshot: Invocation in the absence of concurrent + # updates returns an accurate result, but concurrent updates that + # occur while the sum is being calculated might not be + # incorporated. + def sum + x = base + if current_cells = cells + current_cells.each do |cell| + x += cell.value if cell + end + end + x + end + + def reset + internal_reset(0) + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/atomic_reference.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/atomic_reference.rb new file mode 100644 index 0000000..4d01e9f --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/atomic_reference.rb @@ -0,0 +1,45 @@ +module ThreadSafe + module Util + AtomicReference = + if defined?(Rubinius::AtomicReference) + # An overhead-less atomic reference. + Rubinius::AtomicReference + else + begin + require 'atomic' + defined?(Atomic::InternalReference) ? Atomic::InternalReference : Atomic + rescue LoadError, NameError + require 'thread' # get Mutex on 1.8 + class FullLockingAtomicReference + def initialize(value = nil) + @___mutex = Mutex.new + @___value = value + end + + def get + @___mutex.synchronize { @___value } + end + alias_method :value, :get + + def set(new_value) + @___mutex.synchronize { @___value = new_value } + end + alias_method :value=, :set + + def compare_and_set(old_value, new_value) + return false unless @___mutex.try_lock + begin + return false unless @___value.equal? old_value + @___value = new_value + ensure + @___mutex.unlock + end + true + end + end + + FullLockingAtomicReference + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/cheap_lockable.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/cheap_lockable.rb new file mode 100644 index 0000000..b317ed2 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/cheap_lockable.rb @@ -0,0 +1,105 @@ +module ThreadSafe + module Util + # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ with the +ConditionVariable+ bundled in. + # + # Usage: + # class A + # include CheapLockable + # + # def do_exlusively + # cheap_synchronize { yield } + # end + # + # def wait_for_something + # cheap_synchronize do + # cheap_wait until resource_available? + # do_something + # cheap_broadcast # wake up others + # end + # end + # end + module CheapLockable + private + engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + if engine == 'rbx' + # Making use of the Rubinius' ability to lock via object headers to avoid the overhead of the extra Mutex objects. + def cheap_synchronize + Rubinius.lock(self) + begin + yield + ensure + Rubinius.unlock(self) + end + end + + def cheap_wait + wchan = Rubinius::Channel.new + + begin + waiters = @waiters ||= [] + waiters.push wchan + Rubinius.unlock(self) + signaled = wchan.receive_timeout nil + ensure + Rubinius.lock(self) + + unless signaled or waiters.delete(wchan) + # we timed out, but got signaled afterwards (e.g. while waiting to + # acquire @lock), so pass that signal on to the next waiter + waiters.shift << true unless waiters.empty? + end + end + + self + end + + def cheap_broadcast + waiters = @waiters ||= [] + waiters.shift << true until waiters.empty? + self + end + elsif engine == 'jruby' + # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects + require 'jruby' + + def cheap_synchronize + JRuby.reference0(self).synchronized { yield } + end + + def cheap_wait + JRuby.reference0(self).wait + end + + def cheap_broadcast + JRuby.reference0(self).notify_all + end + else + require 'thread' + + extend Volatile + attr_volatile :mutex + + # Non-reentrant Mutex#syncrhonize + def cheap_synchronize + true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new) + my_mutex.synchronize { yield } + end + + # Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup. + # Must only be called in +cheap_broadcast+'s block. + def cheap_wait + conditional_variable = @conditional_variable ||= ConditionVariable.new + conditional_variable.wait(mutex) + end + + # Wakes up all threads waiting for this object's +cheap_synchronize+ lock. + # Must only be called in +cheap_broadcast+'s block. + def cheap_broadcast + if conditional_variable = @conditional_variable + conditional_variable.broadcast + end + end + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/power_of_two_tuple.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/power_of_two_tuple.rb new file mode 100644 index 0000000..3e2d076 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/power_of_two_tuple.rb @@ -0,0 +1,26 @@ +module ThreadSafe + module Util + class PowerOfTwoTuple < VolatileTuple + def initialize(size) + raise ArgumentError, "size must be a power of 2 (#{size.inspect} provided)" unless size > 0 && size & (size - 1) == 0 + super(size) + end + + def hash_to_index(hash) + (size - 1) & hash + end + + def volatile_get_by_hash(hash) + volatile_get(hash_to_index(hash)) + end + + def volatile_set_by_hash(hash, value) + volatile_set(hash_to_index(hash), value) + end + + def next_in_size_table + self.class.new(size << 1) + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/striped64.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/striped64.rb new file mode 100644 index 0000000..5296204 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/striped64.rb @@ -0,0 +1,226 @@ +module ThreadSafe + module Util + # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6 available in public domain. + # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6 + # + # Class holding common representation and mechanics for classes supporting dynamic striping on 64bit values. + # + # This class maintains a lazily-initialized table of atomically + # updated variables, plus an extra +base+ field. The table size + # is a power of two. Indexing uses masked per-thread hash codes. + # Nearly all methods on this class are private, accessed directly + # by subclasses. + # + # Table entries are of class +Cell+; a variant of AtomicLong padded + # to reduce cache contention on most processors. Padding is + # overkill for most Atomics because they are usually irregularly + # scattered in memory and thus don't interfere much with each + # other. But Atomic objects residing in arrays will tend to be + # placed adjacent to each other, and so will most often share + # cache lines (with a huge negative performance impact) without + # this precaution. + # + # In part because +Cell+s are relatively large, we avoid creating + # them until they are needed. When there is no contention, all + # updates are made to the +base+ field. Upon first contention (a + # failed CAS on +base+ update), the table is initialized to size 2. + # The table size is doubled upon further contention until + # reaching the nearest power of two greater than or equal to the + # number of CPUS. Table slots remain empty (+nil+) until they are + # needed. + # + # A single spinlock (+busy+) is used for initializing and + # resizing the table, as well as populating slots with new +Cell+s. + # There is no need for a blocking lock: When the lock is not + # available, threads try other slots (or the base). During these + # retries, there is increased contention and reduced locality, + # which is still better than alternatives. + # + # Per-thread hash codes are initialized to random values. + # Contention and/or table collisions are indicated by failed + # CASes when performing an update operation (see method + # +retry_update+). Upon a collision, if the table size is less than + # the capacity, it is doubled in size unless some other thread + # holds the lock. If a hashed slot is empty, and lock is + # available, a new +Cell+ is created. Otherwise, if the slot + # exists, a CAS is tried. Retries proceed by "double hashing", + # using a secondary hash (XorShift) to try to find a + # free slot. + # + # The table size is capped because, when there are more threads + # than CPUs, supposing that each thread were bound to a CPU, + # there would exist a perfect hash function mapping threads to + # slots that eliminates collisions. When we reach capacity, we + # search for this mapping by randomly varying the hash codes of + # colliding threads. Because search is random, and collisions + # only become known via CAS failures, convergence can be slow, + # and because threads are typically not bound to CPUS forever, + # may not occur at all. However, despite these limitations, + # observed contention rates are typically low in these cases. + # + # It is possible for a +Cell+ to become unused when threads that + # once hashed to it terminate, as well as in the case where + # doubling the table causes no thread to hash to it under + # expanded mask. We do not try to detect or remove such cells, + # under the assumption that for long-running instances, observed + # contention levels will recur, so the cells will eventually be + # needed again; and for short-lived ones, it does not matter. + class Striped64 + # Padded variant of AtomicLong supporting only raw accesses plus CAS. + # The +value+ field is placed between pads, hoping that the JVM doesn't + # reorder them. + # + # Optimisation note: It would be possible to use a release-only + # form of CAS here, if it were provided. + class Cell < AtomicReference + # TODO: this only adds padding after the :value slot, need to find a way to add padding before the slot + attr_reader *(Array.new(12).map {|i| :"padding_#{i}"}) + + alias_method :cas, :compare_and_set + + def cas_computed + cas(current_value = value, yield(current_value)) + end + end + + extend Volatile + attr_volatile :cells, # Table of cells. When non-null, size is a power of 2. + :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS. + :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells. + + alias_method :busy?, :busy + + def initialize + super() + self.busy = false + self.base = 0 + end + + # Handles cases of updates involving initialization, resizing, + # creating new Cells, and/or contention. See above for + # explanation. This method suffers the usual non-modularity + # problems of optimistic retry code, relying on rechecked sets of + # reads. + # + # Arguments: + # [+x+] + # the value + # [+hash_code+] + # hash code used + # [+x+] + # false if CAS failed before call + def retry_update(x, hash_code, was_uncontended) # :yields: current_value + hash = hash_code + collided = false # True if last slot nonempty + while true + if current_cells = cells + if !(cell = current_cells.volatile_get_by_hash(hash)) + if busy? + collided = false + else # Try to attach new Cell + if try_to_install_new_cell(Cell.new(x), hash) # Optimistically create and try to insert new cell + break + else + redo # Slot is now non-empty + end + end + elsif !was_uncontended # CAS already known to fail + was_uncontended = true # Continue after rehash + elsif cell.cas_computed {|current_value| yield current_value} + break + elsif current_cells.size >= CPU_COUNT || cells != current_cells # At max size or stale + collided = false + elsif collided && expand_table_unless_stale(current_cells) + collided = false + redo # Retry with expanded table + else + collided = true + end + hash = XorShiftRandom.xorshift(hash) + + elsif try_initialize_cells(x, hash) || cas_base_computed {|current_base| yield current_base} + break + end + end + self.hash_code = hash + end + + private + # Static per-thread hash code key. Shared across all instances to + # reduce Thread locals pollution and because adjustments due to + # collisions in one table are likely to be appropriate for + # others. + THREAD_LOCAL_KEY = "#{name}.hash_code".to_sym + + # A thread-local hash code accessor. The code is initially + # random, but may be set to a different value upon collisions. + def hash_code + Thread.current[THREAD_LOCAL_KEY] ||= XorShiftRandom.get + end + + def hash_code=(hash) + Thread.current[THREAD_LOCAL_KEY] = hash + end + + # Sets base and all +cells+ to the given value. + def internal_reset(initial_value) + current_cells = cells + self.base = initial_value + if current_cells + current_cells.each do |cell| + cell.value = initial_value if cell + end + end + end + + def cas_base_computed + cas_base(current_base = base, yield(current_base)) + end + + def free? + !busy? + end + + def try_initialize_cells(x, hash) + if free? && !cells + try_in_busy do + unless cells # Recheck under lock + new_cells = PowerOfTwoTuple.new(2) + new_cells.volatile_set_by_hash(hash, Cell.new(x)) + self.cells = new_cells + end + end + end + end + + def expand_table_unless_stale(current_cells) + try_in_busy do + if current_cells == cells # Recheck under lock + new_cells = current_cells.next_in_size_table + current_cells.each_with_index {|x, i| new_cells.volatile_set(i, x)} + self.cells = new_cells + end + end + end + + def try_to_install_new_cell(new_cell, hash) + try_in_busy do + # Recheck under lock + if (current_cells = cells) && !current_cells.volatile_get(i = current_cells.hash_to_index(hash)) + current_cells.volatile_set(i, new_cell) + end + end + end + + def try_in_busy + if cas_busy(false, true) + begin + yield + ensure + self.busy = false + end + end + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile.rb new file mode 100644 index 0000000..8a8b9fb --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile.rb @@ -0,0 +1,62 @@ +module ThreadSafe + module Util + module Volatile + # Provides +volatile+ (in the JVM's sense) attribute accessors implemented atop of the +AtomicReference+s. + # Usage: + # class Foo + # extend ThreadSafe::Util::Volatile + # attr_volatile :foo, :bar + # + # def initialize(bar) + # super() # must super() into parent initializers before using the volatile attribute accessors + # self.bar = bar + # end + # + # def hello + # my_foo = foo # volatile read + # self.foo = 1 # volatile write + # cas_foo(1, 2) # => true | a strong CAS + # end + # end + def attr_volatile(*attr_names) + return if attr_names.empty? + include(Module.new do + atomic_ref_setup = attr_names.map {|attr_name| "@__#{attr_name} = ThreadSafe::Util::AtomicReference.new"} + initialize_copy_setup = attr_names.zip(atomic_ref_setup).map do |attr_name, ref_setup| + "#{ref_setup}(other.instance_variable_get(:@__#{attr_name}).get)" + end + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def initialize(*) + super + #{atomic_ref_setup.join('; ')} + end + + def initialize_copy(other) + super + #{initialize_copy_setup.join('; ')} + end + RUBY_EVAL + + attr_names.each do |attr_name| + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{attr_name} + @__#{attr_name}.get + end + + def #{attr_name}=(value) + @__#{attr_name}.set(value) + end + + def compare_and_set_#{attr_name}(old_value, new_value) + @__#{attr_name}.compare_and_set(old_value, new_value) + end + RUBY_EVAL + + alias_method :"cas_#{attr_name}", :"compare_and_set_#{attr_name}" + alias_method :"lazy_set_#{attr_name}", :"#{attr_name}=" + end + end) + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile_tuple.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile_tuple.rb new file mode 100644 index 0000000..500beb7 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/volatile_tuple.rb @@ -0,0 +1,46 @@ +module ThreadSafe + module Util + # A fixed size array with volatile volatile getters/setters. + # Usage: + # arr = VolatileTuple.new(16) + # arr.volatile_set(0, :foo) + # arr.volatile_get(0) # => :foo + # arr.cas(0, :foo, :bar) # => true + # arr.volatile_get(0) # => :bar + class VolatileTuple + include Enumerable + + Tuple = defined?(Rubinius::Tuple) ? Rubinius::Tuple : Array + + def initialize(size) + @tuple = tuple = Tuple.new(size) + i = 0 + while i < size + tuple[i] = AtomicReference.new + i += 1 + end + end + + def volatile_get(i) + @tuple[i].get + end + + def volatile_set(i, value) + @tuple[i].set(value) + end + + def compare_and_set(i, old_value, new_value) + @tuple[i].compare_and_set(old_value, new_value) + end + alias_method :cas, :compare_and_set + + def size + @tuple.size + end + + def each + @tuple.each {|ref| yield ref.get} + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/xor_shift_random.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/xor_shift_random.rb new file mode 100644 index 0000000..ad8bf7b --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/util/xor_shift_random.rb @@ -0,0 +1,39 @@ +module ThreadSafe + module Util + # A xorshift random number (positive +Fixnum+s) generator, provides reasonably cheap way to generate thread local random numbers without contending for + # the global +Kernel.rand+. + # Usage: + # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed + # while true + # if (x = XorShiftRandom.xorshift).odd? # thread-localy generate a next random number + # do_something_at_random + # end + # end + module XorShiftRandom + extend self + MAX_XOR_SHIFTABLE_INT = MAX_INT - 1 + + # Generates an initial non-zero positive +Fixnum+ via +Kernel.rand+. + def get + Kernel.rand(MAX_XOR_SHIFTABLE_INT) + 1 # 0 can't be xorshifted + end + + # xorshift based on: http://www.jstatsoft.org/v08/i14/paper + if 0.size == 4 + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (3,1,14) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 3 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 14 + end + else + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (1,1,54) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 1 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 54 + end + end + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/lib/thread_safe/version.rb b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/version.rb new file mode 100644 index 0000000..e5e7c92 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/lib/thread_safe/version.rb @@ -0,0 +1,21 @@ +module ThreadSafe + VERSION = "0.3.4" +end + +# NOTE: <= 0.2.0 used Threadsafe::VERSION +# @private +module Threadsafe + + # @private + def self.const_missing(name) + name = name.to_sym + if ThreadSafe.const_defined?(name) + warn "[DEPRECATION] `Threadsafe::#{name}' is deprecated, use `ThreadSafe::#{name}' instead." + ThreadSafe.const_get(name) + else + warn "[DEPRECATION] the `Threadsafe' module is deprecated, please use `ThreadSafe` instead." + super + end + end + +end diff --git a/.gems/gems/thread_safe-0.3.4/test/src/thread_safe/SecurityManager.java b/.gems/gems/thread_safe-0.3.4/test/src/thread_safe/SecurityManager.java new file mode 100644 index 0000000..beb6c02 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/src/thread_safe/SecurityManager.java @@ -0,0 +1,21 @@ +package thread_safe; + +import java.security.Permission; +import java.util.ArrayList; +import java.util.List; + +public class SecurityManager extends java.lang.SecurityManager { + private final List deniedPermissions = + new ArrayList(); + + @Override + public void checkPermission(Permission p) { + if (deniedPermissions.contains(p)) { + throw new SecurityException("Denied!"); + } + } + + public void deny(Permission p) { + deniedPermissions.add(p); + } +} diff --git a/.gems/gems/thread_safe-0.3.4/test/test_array.rb b/.gems/gems/thread_safe-0.3.4/test/test_array.rb new file mode 100644 index 0000000..7a6023a --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_array.rb @@ -0,0 +1,18 @@ +require 'thread_safe' +require File.join(File.dirname(__FILE__), "test_helper") + +class TestArray < Minitest::Test + def test_concurrency + ary = ThreadSafe::Array.new + (1..100).map do |i| + Thread.new do + 1000.times do + ary << i + ary.each {|x| x * 2} + ary.shift + ary.last + end + end + end.map(&:join) + end +end diff --git a/.gems/gems/thread_safe-0.3.4/test/test_cache.rb b/.gems/gems/thread_safe-0.3.4/test/test_cache.rb new file mode 100644 index 0000000..1dfb42d --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_cache.rb @@ -0,0 +1,901 @@ +require 'thread_safe' +require 'thread' +require File.join(File.dirname(__FILE__), "test_helper") + +Thread.abort_on_exception = true + +class TestCache < Minitest::Test + def setup + @cache = ThreadSafe::Cache.new + end + + def test_concurrency + cache = @cache + (1..100).map do |i| + Thread.new do + 1000.times do |j| + key = i*1000+j + cache[key] = i + cache[key] + cache.delete(key) + end + end + end.map(&:join) + end + + def test_retrieval + assert_size_change 1 do + assert_equal nil, @cache[:a] + assert_equal nil, @cache.get(:a) + @cache[:a] = 1 + assert_equal 1, @cache[:a] + assert_equal 1, @cache.get(:a) + end + end + + def test_put_if_absent + with_or_without_default_proc do + assert_size_change 1 do + assert_equal nil, @cache.put_if_absent(:a, 1) + assert_equal 1, @cache.put_if_absent(:a, 1) + assert_equal 1, @cache.put_if_absent(:a, 2) + assert_equal 1, @cache[:a] + end + end + end + + def test_compute_if_absent + with_or_without_default_proc do + assert_size_change 3 do + assert_equal(1, (@cache.compute_if_absent(:a) {1})) + assert_equal(1, (@cache.compute_if_absent(:a) {2})) + assert_equal 1, @cache[:a] + @cache[:b] = nil + assert_equal(nil, (@cache.compute_if_absent(:b) {1})) + assert_equal(nil, (@cache.compute_if_absent(:c) {})) + assert_equal nil, @cache[:c] + assert_equal true, @cache.key?(:c) + end + end + end + + def test_compute_if_absent_with_return + with_or_without_default_proc { assert_handles_return_lambda(:compute_if_absent, :a) } + end + + def test_compute_if_absent_exception + with_or_without_default_proc { assert_handles_exception(:compute_if_absent, :a) } + end + + def test_compute_if_absent_atomicity + late_compute_threads_count = 10 + late_put_if_absent_threads_count = 10 + getter_threads_count = 5 + compute_started = ThreadSafe::Test::Latch.new(1) + compute_proceed = ThreadSafe::Test::Latch.new(late_compute_threads_count + late_put_if_absent_threads_count + getter_threads_count) + block_until_compute_started = lambda do |name| + if (v = @cache[:a]) != nil + assert_equal nil, v + end + compute_proceed.release + compute_started.await + end + + assert_size_change 1 do + late_compute_threads = Array.new(late_compute_threads_count) do + Thread.new do + block_until_compute_started.call('compute_if_absent') + assert_equal(1, (@cache.compute_if_absent(:a) { flunk })) + end + end + + late_put_if_absent_threads = Array.new(late_put_if_absent_threads_count) do + Thread.new do + block_until_compute_started.call('put_if_absent') + assert_equal(1, @cache.put_if_absent(:a, 2)) + end + end + + getter_threads = Array.new(getter_threads_count) do + Thread.new do + block_until_compute_started.call('getter') + Thread.pass while @cache[:a].nil? + assert_equal 1, @cache[:a] + end + end + + Thread.new do + @cache.compute_if_absent(:a) do + compute_started.release + compute_proceed.await + sleep(0.2) + 1 + end + end.join + (late_compute_threads + late_put_if_absent_threads + getter_threads).each(&:join) + end + end + + def test_compute_if_present + with_or_without_default_proc do + assert_no_size_change do + assert_equal(nil, @cache.compute_if_present(:a) {}) + assert_equal(nil, @cache.compute_if_present(:a) {1}) + assert_equal(nil, @cache.compute_if_present(:a) {flunk}) + assert_equal false, @cache.key?(:a) + end + + @cache[:a] = 1 + assert_no_size_change do + assert_equal(1, @cache.compute_if_present(:a) {1}) + assert_equal(1, @cache[:a]) + assert_equal(2, @cache.compute_if_present(:a) {2}) + assert_equal(2, @cache[:a]) + assert_equal(false, @cache.compute_if_present(:a) {false}) + assert_equal(false, @cache[:a]) + + @cache[:a] = 1 + yielded = false + @cache.compute_if_present(:a) do |old_value| + yielded = true + assert_equal 1, old_value + 2 + end + assert yielded + end + + assert_size_change -1 do + assert_equal(nil, @cache.compute_if_present(:a) {}) + assert_equal(false, @cache.key?(:a)) + assert_equal(nil, @cache.compute_if_present(:a) {1}) + assert_equal(false, @cache.key?(:a)) + end + end + end + + def test_compute_if_present_with_return + with_or_without_default_proc do + @cache[:a] = 1 + assert_handles_return_lambda(:compute_if_present, :a) + end + end + + def test_compute_if_present_exception + with_or_without_default_proc do + @cache[:a] = 1 + assert_handles_exception(:compute_if_present, :a) + end + end + + def test_compute + with_or_without_default_proc do + assert_no_size_change do + assert_compute(:a, nil, nil) {} + end + + assert_size_change 1 do + assert_compute(:a, nil, 1) {1} + assert_compute(:a, 1, 2) {2} + assert_compute(:a, 2, false) {false} + assert_equal false, @cache[:a] + end + + assert_size_change -1 do + assert_compute(:a, false, nil) {} + end + end + end + + def test_compute_with_return + with_or_without_default_proc do + assert_handles_return_lambda(:compute, :a) + @cache[:a] = 1 + assert_handles_return_lambda(:compute, :a) + end + end + + def test_compute_exception + with_or_without_default_proc do + assert_handles_exception(:compute, :a) + @cache[:a] = 1 + assert_handles_exception(:compute, :a) + end + end + + def test_merge_pair + with_or_without_default_proc do + assert_size_change 1 do + assert_equal(nil, @cache.merge_pair(:a, nil) {flunk}) + assert_equal true, @cache.key?(:a) + assert_equal nil, @cache[:a] + end + + assert_no_size_change do + assert_merge_pair(:a, nil, nil, false) {false} + assert_merge_pair(:a, nil, false, 1) {1} + assert_merge_pair(:a, nil, 1, 2) {2} + end + + assert_size_change -1 do + assert_merge_pair(:a, nil, 2, nil) {} + assert_equal false, @cache.key?(:a) + end + end + end + + def test_merge_pair_with_return + with_or_without_default_proc do + @cache[:a] = 1 + assert_handles_return_lambda(:merge_pair, :a, 2) + end + end + + def test_merge_pair_exception + with_or_without_default_proc do + @cache[:a] = 1 + assert_handles_exception(:merge_pair, :a, 2) + end + end + + def test_updates_dont_block_reads + getters_count = 20 + key_klass = ThreadSafe::Test::HashCollisionKey + keys = [key_klass.new(1, 100), key_klass.new(2, 100), key_klass.new(3, 100)] # hash colliding keys + inserted_keys = [] + + keys.each do |key, i| + compute_started = ThreadSafe::Test::Latch.new(1) + compute_finished = ThreadSafe::Test::Latch.new(1) + getters_started = ThreadSafe::Test::Latch.new(getters_count) + getters_finished = ThreadSafe::Test::Latch.new(getters_count) + + computer_thread = Thread.new do + getters_started.await + @cache.compute_if_absent(key) do + compute_started.release + getters_finished.await + 1 + end + compute_finished.release + end + + getter_threads = (1..getters_count).map do + Thread.new do + getters_started.release + inserted_keys.each do |inserted_key| + assert_equal true, @cache.key?(inserted_key) + assert_equal 1, @cache[inserted_key] + end + assert_equal false, @cache.key?(key) + compute_started.await + inserted_keys.each do |inserted_key| + assert_equal true, @cache.key?(inserted_key) + assert_equal 1, @cache[inserted_key] + end + assert_equal false, @cache.key?(key) + assert_equal nil, @cache[key] + getters_finished.release + compute_finished.await + assert_equal true, @cache.key?(key) + assert_equal 1, @cache[key] + end + end + + (getter_threads << computer_thread).map {|t| assert(t.join(2))} # asserting no deadlocks + inserted_keys << key + end + end + + def test_collision_resistance + assert_collision_resistance((0..1000).map {|i| ThreadSafe::Test::HashCollisionKey(i, 1)}) + end + + def test_collision_resistance_with_arrays + special_array_class = Class.new(Array) do + def key # assert_collision_resistance expects to be able to call .key to get the "real" key + first.key + end + end + # Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions + # when actually called (ie: an Array filled with non-comparable keys). + # See https://github.com/headius/thread_safe/issues/19 for more info. + assert_collision_resistance((0..100).map do |i| + special_array_class.new([ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)]) + end) + end + + def test_replace_pair + with_or_without_default_proc do + assert_no_size_change do + assert_equal false, @cache.replace_pair(:a, 1, 2) + assert_equal false, @cache.replace_pair(:a, nil, nil) + assert_equal false, @cache.key?(:a) + end + + @cache[:a] = 1 + assert_no_size_change do + assert_equal true, @cache.replace_pair(:a, 1, 2) + assert_equal false, @cache.replace_pair(:a, 1, 2) + assert_equal 2, @cache[:a] + assert_equal true, @cache.replace_pair(:a, 2, 2) + assert_equal 2, @cache[:a] + assert_equal true, @cache.replace_pair(:a, 2, nil) + assert_equal false, @cache.replace_pair(:a, 2, nil) + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + assert_equal true, @cache.replace_pair(:a, nil, nil) + assert_equal true, @cache.key?(:a) + assert_equal true, @cache.replace_pair(:a, nil, 1) + assert_equal 1, @cache[:a] + end + end + end + + def test_replace_if_exists + with_or_without_default_proc do + assert_no_size_change do + assert_equal nil, @cache.replace_if_exists(:a, 1) + assert_equal false, @cache.key?(:a) + end + + @cache[:a] = 1 + assert_no_size_change do + assert_equal 1, @cache.replace_if_exists(:a, 2) + assert_equal 2, @cache[:a] + assert_equal 2, @cache.replace_if_exists(:a, nil) + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + assert_equal nil, @cache.replace_if_exists(:a, 1) + assert_equal 1, @cache[:a] + end + end + end + + def test_get_and_set + with_or_without_default_proc do + assert_size_change 1 do + assert_equal nil, @cache.get_and_set(:a, 1) + assert_equal true, @cache.key?(:a) + assert_equal 1, @cache[:a] + assert_equal 1, @cache.get_and_set(:a, 2) + assert_equal 2, @cache.get_and_set(:a, nil) + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + assert_equal nil, @cache.get_and_set(:a, 1) + assert_equal 1, @cache[:a] + end + end + end + + def test_key + with_or_without_default_proc do + assert_equal nil, @cache.key(1) + @cache[:a] = 1 + assert_equal :a, @cache.key(1) + assert_equal nil, @cache.key(0) + assert_equal :a, @cache.index(1) if RUBY_VERSION =~ /1\.8/ + end + end + + def test_key? + with_or_without_default_proc do + assert_equal false, @cache.key?(:a) + @cache[:a] = 1 + assert_equal true, @cache.key?(:a) + end + end + + def test_value? + with_or_without_default_proc do + assert_equal false, @cache.value?(1) + @cache[:a] = 1 + assert_equal true, @cache.value?(1) + end + end + + def test_delete + with_or_without_default_proc do |default_proc_set| + assert_no_size_change do + assert_equal nil, @cache.delete(:a) + end + @cache[:a] = 1 + assert_size_change -1 do + assert_equal 1, @cache.delete(:a) + end + assert_no_size_change do + assert_equal nil, @cache[:a] unless default_proc_set + + assert_equal false, @cache.key?(:a) + assert_equal nil, @cache.delete(:a) + end + end + end + + def test_delete_pair + with_or_without_default_proc do + assert_no_size_change do + assert_equal false, @cache.delete_pair(:a, 2) + assert_equal false, @cache.delete_pair(:a, nil) + end + @cache[:a] = 1 + assert_no_size_change do + assert_equal false, @cache.delete_pair(:a, 2) + end + assert_size_change -1 do + assert_equal 1, @cache[:a] + assert_equal true, @cache.delete_pair(:a, 1) + assert_equal false, @cache.delete_pair(:a, 1) + assert_equal false, @cache.key?(:a) + end + end + end + + def test_default_proc + @cache = cache_with_default_proc(1) + assert_no_size_change do + assert_equal false, @cache.key?(:a) + end + assert_size_change 1 do + assert_equal 1, @cache[:a] + assert_equal true, @cache.key?(:a) + end + end + + def test_falsy_default_proc + @cache = cache_with_default_proc(nil) + assert_no_size_change do + assert_equal false, @cache.key?(:a) + end + assert_size_change 1 do + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + end + end + + def test_fetch + with_or_without_default_proc do |default_proc_set| + assert_no_size_change do + assert_equal 1, @cache.fetch(:a, 1) + assert_equal(1, (@cache.fetch(:a) {1})) + assert_equal false, @cache.key?(:a) + + assert_equal nil, @cache[:a] unless default_proc_set + end + + @cache[:a] = 1 + assert_no_size_change do + assert_equal(1, (@cache.fetch(:a) {flunk})) + end + + assert_raises(ThreadSafe::Cache::KEY_ERROR) do + @cache.fetch(:b) + end + + assert_no_size_change do + assert_equal 1, (@cache.fetch(:b, :c) {1}) # assert block supersedes default value argument + assert_equal false, @cache.key?(:b) + end + end + end + + def test_falsy_fetch + with_or_without_default_proc do + assert_equal false, @cache.key?(:a) + + assert_no_size_change do + assert_equal(nil, @cache.fetch(:a, nil)) + assert_equal(false, @cache.fetch(:a, false)) + assert_equal(nil, (@cache.fetch(:a) {})) + assert_equal(false, (@cache.fetch(:a) {false})) + end + + @cache[:a] = nil + assert_no_size_change do + assert_equal true, @cache.key?(:a) + assert_equal(nil, (@cache.fetch(:a) {flunk})) + end + end + end + + def test_fetch_with_return + with_or_without_default_proc do + r = lambda do + @cache.fetch(:a) { return 10 } + end.call + + assert_no_size_change do + assert_equal 10, r + assert_equal false, @cache.key?(:a) + end + end + end + + def test_fetch_or_store + with_or_without_default_proc do |default_proc_set| + assert_size_change 1 do + assert_equal 1, @cache.fetch_or_store(:a, 1) + assert_equal 1, @cache[:a] + end + + @cache.delete(:a) + + assert_size_change 1 do + assert_equal 1, (@cache.fetch_or_store(:a) {1}) + assert_equal 1, @cache[:a] + end + + assert_no_size_change do + assert_equal(1, (@cache.fetch_or_store(:a) {flunk})) + end + + assert_raises(ThreadSafe::Cache::KEY_ERROR) do + @cache.fetch_or_store(:b) + end + + assert_size_change 1 do + assert_equal 1, (@cache.fetch_or_store(:b, :c) {1}) # assert block supersedes default value argument + assert_equal 1, @cache[:b] + end + end + end + + def test_falsy_fetch_or_store + with_or_without_default_proc do + assert_equal false, @cache.key?(:a) + + assert_size_change 1 do + assert_equal(nil, @cache.fetch_or_store(:a, nil)) + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + end + @cache.delete(:a) + + assert_size_change 1 do + assert_equal(false, @cache.fetch_or_store(:a, false)) + assert_equal false, @cache[:a] + assert_equal true, @cache.key?(:a) + end + @cache.delete(:a) + + assert_size_change 1 do + assert_equal(nil, (@cache.fetch_or_store(:a) {})) + assert_equal nil, @cache[:a] + assert_equal true, @cache.key?(:a) + end + @cache.delete(:a) + + assert_size_change 1 do + assert_equal(false, (@cache.fetch_or_store(:a) {false})) + assert_equal false, @cache[:a] + assert_equal true, @cache.key?(:a) + end + + @cache[:a] = nil + assert_no_size_change do + assert_equal(nil, (@cache.fetch_or_store(:a) {flunk})) + end + end + end + + def test_fetch_or_store_with_return + with_or_without_default_proc do + r = lambda do + @cache.fetch_or_store(:a) { return 10 } + end.call + + assert_no_size_change do + assert_equal 10, r + assert_equal false, @cache.key?(:a) + end + end + end + + def test_clear + @cache[:a] = 1 + assert_size_change -1 do + assert_equal @cache, @cache.clear + assert_equal false, @cache.key?(:a) + assert_equal nil, @cache[:a] + end + end + + def test_each_pair + @cache.each_pair {|k, v| flunk} + assert_equal(@cache, (@cache.each_pair {})) + @cache[:a] = 1 + + h = {} + @cache.each_pair {|k, v| h[k] = v} + assert_equal({:a => 1}, h) + + @cache[:b] = 2 + h = {} + @cache.each_pair {|k, v| h[k] = v} + assert_equal({:a => 1, :b => 2}, h) + end + + def test_each_pair_iterator + @cache[:a] = 1 + @cache[:b] = 2 + i = 0 + r = @cache.each_pair do |k, v| + if i == 0 + i += 1 + next + flunk + elsif i == 1 + break :breaked + end + end + + assert_equal :breaked, r + end + + def test_each_pair_allows_modification + @cache[:a] = 1 + @cache[:b] = 1 + @cache[:c] = 1 + + assert_size_change 1 do + @cache.each_pair do |k, v| + @cache[:z] = 1 + end + end + end + + def test_keys + assert_equal [], @cache.keys + + @cache[1] = 1 + assert_equal [1], @cache.keys + + @cache[2] = 2 + assert_equal [1, 2], @cache.keys.sort + end + + def test_values + assert_equal [], @cache.values + + @cache[1] = 1 + assert_equal [1], @cache.values + + @cache[2] = 2 + assert_equal [1, 2], @cache.values.sort + end + + def test_each_key + assert_equal(@cache, (@cache.each_key {flunk})) + + @cache[1] = 1 + arr = [] + @cache.each_key {|k| arr << k} + assert_equal [1], arr + + @cache[2] = 2 + arr = [] + @cache.each_key {|k| arr << k} + assert_equal [1, 2], arr.sort + end + + def test_each_value + assert_equal(@cache, (@cache.each_value {flunk})) + + @cache[1] = 1 + arr = [] + @cache.each_value {|k| arr << k} + assert_equal [1], arr + + @cache[2] = 2 + arr = [] + @cache.each_value {|k| arr << k} + assert_equal [1, 2], arr.sort + end + + def test_empty + assert_equal true, @cache.empty? + @cache[:a] = 1 + assert_equal false, @cache.empty? + end + + def test_options_validation + assert_valid_options(nil) + assert_valid_options({}) + assert_valid_options(:foo => :bar) + end + + def test_initial_capacity_options_validation + assert_valid_option(:initial_capacity, nil) + assert_valid_option(:initial_capacity, 1) + assert_invalid_option(:initial_capacity, '') + assert_invalid_option(:initial_capacity, 1.0) + assert_invalid_option(:initial_capacity, -1) + end + + def test_load_factor_options_validation + assert_valid_option(:load_factor, nil) + assert_valid_option(:load_factor, 0.01) + assert_valid_option(:load_factor, 0.75) + assert_valid_option(:load_factor, 1) + assert_invalid_option(:load_factor, '') + assert_invalid_option(:load_factor, 0) + assert_invalid_option(:load_factor, 1.1) + assert_invalid_option(:load_factor, 2) + assert_invalid_option(:load_factor, -1) + end + + def test_size + assert_equal 0, @cache.size + @cache[:a] = 1 + assert_equal 1, @cache.size + @cache[:b] = 1 + assert_equal 2, @cache.size + @cache.delete(:a) + assert_equal 1, @cache.size + @cache.delete(:b) + assert_equal 0, @cache.size + end + + def test_get_or_default + with_or_without_default_proc do + assert_equal 1, @cache.get_or_default(:a, 1) + assert_equal nil, @cache.get_or_default(:a, nil) + assert_equal false, @cache.get_or_default(:a, false) + assert_equal false, @cache.key?(:a) + + @cache[:a] = 1 + assert_equal 1, @cache.get_or_default(:a, 2) + end + end + + def test_dup_clone + [:dup, :clone].each do |meth| + cache = cache_with_default_proc(:default_value) + cache[:a] = 1 + dupped = cache.send(meth) + assert_equal 1, dupped[:a] + assert_equal 1, dupped.size + assert_size_change 1, cache do + assert_no_size_change dupped do + cache[:b] = 1 + end + end + assert_equal false, dupped.key?(:b) + assert_no_size_change cache do + assert_size_change -1, dupped do + dupped.delete(:a) + end + end + assert_equal false, dupped.key?(:a) + assert_equal true, cache.key?(:a) + # test default proc + assert_size_change 1, cache do + assert_no_size_change dupped do + assert_equal :default_value, cache[:c] + assert_equal false, dupped.key?(:c) + end + end + assert_no_size_change cache do + assert_size_change 1, dupped do + assert_equal :default_value, dupped[:d] + assert_equal false, cache.key?(:d) + end + end + end + end + + def test_is_unfreezable + assert_raises(NoMethodError) { @cache.freeze } + end + + def test_marshal_dump_load + new_cache = Marshal.load(Marshal.dump(@cache)) + assert_instance_of ThreadSafe::Cache, new_cache + assert_equal 0, new_cache.size + @cache[:a] = 1 + new_cache = Marshal.load(Marshal.dump(@cache)) + assert_equal 1, @cache[:a] + assert_equal 1, new_cache.size + end + + def test_marshal_dump_doesnt_work_with_default_proc + assert_raises(TypeError) do + Marshal.dump(ThreadSafe::Cache.new {}) + end + end + + private + def with_or_without_default_proc + yield false + @cache = ThreadSafe::Cache.new {|h, k| h[k] = :default_value} + yield true + end + + def cache_with_default_proc(default_value = 1) + ThreadSafe::Cache.new {|cache, k| cache[k] = default_value} + end + + def assert_valid_option(option_name, value) + assert_valid_options(option_name => value) + end + + def assert_valid_options(options) + c = ThreadSafe::Cache.new(options) + assert_instance_of ThreadSafe::Cache, c + end + + def assert_invalid_option(option_name, value) + assert_invalid_options(option_name => value) + end + + def assert_invalid_options(options) + assert_raises(ArgumentError) { ThreadSafe::Cache.new(options) } + end + + def assert_size_change(change, cache = @cache) + start = cache.size + yield + assert_equal change, cache.size - start + end + + def assert_no_size_change(cache = @cache, &block) + assert_size_change(0, cache, &block) + end + + def assert_handles_return_lambda(method, key, *args) + before_had_key = @cache.key?(key) + before_had_value = before_had_key ? @cache[key] : nil + + returning_lambda = lambda do + @cache.send(method, key, *args) { return :direct_return } + end + + assert_no_size_change do + assert_equal(:direct_return, returning_lambda.call) + assert_equal before_had_key, @cache.key?(key) + assert_equal before_had_value, @cache[key] if before_had_value + end + end + + class TestException < Exception; end + def assert_handles_exception(method, key, *args) + before_had_key = @cache.key?(key) + before_had_value = before_had_key ? @cache[key] : nil + + assert_no_size_change do + assert_raises(TestException) do + @cache.send(method, key, *args) { raise TestException, '' } + end + assert_equal before_had_key, @cache.key?(key) + assert_equal before_had_value, @cache[key] if before_had_value + end + end + + def assert_compute(key, expected_old_value, expected_result) + result = @cache.compute(:a) do |old_value| + assert_equal expected_old_value, old_value + yield + end + assert_equal expected_result, result + end + + def assert_merge_pair(key, value, expected_old_value, expected_result) + result = @cache.merge_pair(key, value) do |old_value| + assert_equal expected_old_value, old_value + yield + end + assert_equal expected_result, result + end + + def assert_collision_resistance(keys) + keys.each {|k| @cache[k] = k.key} + 10.times do |i| + size = keys.size + while i < size + k = keys[i] + assert(k.key == @cache.delete(k) && !@cache.key?(k) && (@cache[k] = k.key; @cache[k] == k.key)) + i += 10 + end + end + assert(keys.all? {|k| @cache[k] == k.key}) + end +end diff --git a/.gems/gems/thread_safe-0.3.4/test/test_cache_loops.rb b/.gems/gems/thread_safe-0.3.4/test/test_cache_loops.rb new file mode 100644 index 0000000..e3a92ed --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_cache_loops.rb @@ -0,0 +1,449 @@ +require 'thread' +require 'thread_safe' +require File.join(File.dirname(__FILE__), "test_helper") + +Thread.abort_on_exception = true + +class TestCacheTorture < Minitest::Test # this is not run unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS'] (see the end of the file) + THREAD_COUNT = 40 + KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff + LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff + + INITIAL_VALUE_CACHE_SETUP = lambda do |options, keys| + cache = ThreadSafe::Cache.new + initial_value = options[:initial_value] || 0 + keys.each {|key| cache[key] = initial_value} + cache + end + ZERO_VALUE_CACHE_SETUP = lambda do |options, keys| + INITIAL_VALUE_CACHE_SETUP.call(options.merge(:initial_value => 0), keys) + end + + DEFAULTS = { + :key_count => KEY_COUNT, + :thread_count => THREAD_COUNT, + :loop_count => 1, + :prelude => '', + :cache_setup => lambda {|options, keys| ThreadSafe::Cache.new} + } + + LOW_KEY_COUNT_OPTIONS = {:loop_count => 150, :key_count => LOW_KEY_COUNT} + SINGLE_KEY_COUNT_OPTIONS = {:loop_count => 100_000, :key_count => 1} + + def test_concurrency + code = <<-RUBY_EVAL + cache[key] + cache[key] = key + cache[key] + cache.delete(key) + RUBY_EVAL + do_thread_loop(__method__, code) + end + + def test_put_if_absent + do_thread_loop(__method__, 'acc += 1 unless cache.put_if_absent(key, key)', :key_count => 100_000) do |result, cache, options, keys| + assert_standard_accumulator_test_result(result, cache, options, keys) + end + end + + def test_compute_if_absent + code = 'cache.compute_if_absent(key) { acc += 1; key }' + do_thread_loop(__method__, code) do |result, cache, options, keys| + assert_standard_accumulator_test_result(result, cache, options, keys) + end + end + + def test_compute_put_if_absent + code = <<-RUBY_EVAL + if key.even? + cache.compute_if_absent(key) { acc += 1; key } + else + acc += 1 unless cache.put_if_absent(key, key) + end + RUBY_EVAL + do_thread_loop(__method__, code) do |result, cache, options, keys| + assert_standard_accumulator_test_result(result, cache, options, keys) + end + end + + def test_compute_if_absent_and_present + compute_if_absent_and_present + compute_if_absent_and_present(LOW_KEY_COUNT_OPTIONS) + compute_if_absent_and_present(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_add_remove_to_zero + add_remove_to_zero + add_remove_to_zero(LOW_KEY_COUNT_OPTIONS) + add_remove_to_zero(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_add_remove_to_zero_via_merge_pair + add_remove_to_zero_via_merge_pair + add_remove_to_zero_via_merge_pair(LOW_KEY_COUNT_OPTIONS) + add_remove_to_zero_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_add_remove + add_remove + add_remove(LOW_KEY_COUNT_OPTIONS) + add_remove(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_add_remove_via_compute + add_remove_via_compute + add_remove_via_compute(LOW_KEY_COUNT_OPTIONS) + add_remove_via_compute(SINGLE_KEY_COUNT_OPTIONS) + end + + def add_remove_via_compute_if_absent_present + add_remove_via_compute_if_absent_present + add_remove_via_compute_if_absent_present(LOW_KEY_COUNT_OPTIONS) + add_remove_via_compute_if_absent_present(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_add_remove_indiscriminate + add_remove_indiscriminate + add_remove_indiscriminate(LOW_KEY_COUNT_OPTIONS) + add_remove_indiscriminate(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_count_up + count_up + count_up(LOW_KEY_COUNT_OPTIONS) + count_up(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_count_up_via_compute + count_up_via_compute + count_up_via_compute(LOW_KEY_COUNT_OPTIONS) + count_up_via_compute(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_count_up_via_merge_pair + count_up_via_merge_pair + count_up_via_merge_pair(LOW_KEY_COUNT_OPTIONS) + count_up_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS) + end + + def test_count_race + prelude = 'change = (rand(2) == 1) ? 1 : -1' + code = <<-RUBY_EVAL + v = cache[key] + acc += change if cache.replace_pair(key, v, v + change) + RUBY_EVAL + do_thread_loop(__method__, code, :loop_count => 5, :prelude => prelude, :cache_setup => ZERO_VALUE_CACHE_SETUP) do |result, cache, options, keys| + result_sum = sum(result) + assert_equal(sum(keys.map {|key| cache[key]}), result_sum) + assert_equal(sum(cache.values), result_sum) + assert_equal(options[:key_count], cache.size) + end + end + + def test_get_and_set_new + code = 'acc += 1 unless cache.get_and_set(key, key)' + do_thread_loop(__method__, code) do |result, cache, options, keys| + assert_standard_accumulator_test_result(result, cache, options, keys) + end + end + + def test_get_and_set_existing + code = 'acc += 1 if cache.get_and_set(key, key) == -1' + do_thread_loop(__method__, code, :cache_setup => INITIAL_VALUE_CACHE_SETUP, :initial_value => -1) do |result, cache, options, keys| + assert_standard_accumulator_test_result(result, cache, options, keys) + end + end + + private + def compute_if_absent_and_present(opts = {}) + prelude = 'on_present = rand(2) == 1' + code = <<-RUBY_EVAL + if on_present + cache.compute_if_present(key) {|old_value| acc += 1; old_value + 1} + else + cache.compute_if_absent(key) { acc += 1; 1 } + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys| + stored_sum = 0 + stored_key_count = 0 + keys.each do |k| + if value = cache[k] + stored_sum += value + stored_key_count += 1 + end + end + assert_equal(stored_sum, sum(result)) + assert_equal(stored_key_count, cache.size) + end + end + + def add_remove(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + acc += 1 unless cache.put_if_absent(key, key) + else + acc -= 1 if cache.delete_pair(key, key) + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def add_remove_via_compute(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + cache.compute(key) do |old_value| + if do_add + acc += 1 unless old_value + key + else + acc -= 1 if old_value + nil + end + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def add_remove_via_compute_if_absent_present(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + cache.compute_if_absent(key) { acc += 1; key } + else + cache.compute_if_present(key) { acc -= 1; nil } + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def add_remove_indiscriminate(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + acc += 1 unless cache.put_if_absent(key, key) + else + acc -= 1 if cache.delete(key) + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def count_up(opts = {}) + code = <<-RUBY_EVAL + v = cache[key] + acc += 1 if cache.replace_pair(key, v, v + 1) + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5, :cache_setup => ZERO_VALUE_CACHE_SETUP}.merge(opts)) do |result, cache, options, keys| + assert_count_up(result, cache, options, keys) + end + end + + def count_up_via_compute(opts = {}) + code = <<-RUBY_EVAL + cache.compute(key) do |old_value| + acc += 1 + old_value ? old_value + 1 : 1 + end + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys| + assert_count_up(result, cache, options, keys) + result.inject(nil) do |previous_value, next_value| # since compute guarantees atomicity all count ups should be equal + assert_equal previous_value, next_value if previous_value + next_value + end + end + end + + def count_up_via_merge_pair(opts = {}) + code = <<-RUBY_EVAL + cache.merge_pair(key, 1) {|old_value| old_value + 1} + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys| + all_match = true + expected_value = options[:loop_count] * options[:thread_count] + keys.each do |key| + if expected_value != (value = cache[key]) + all_match = false + break + end + end + assert all_match + end + end + + def add_remove_to_zero(opts = {}) + code = <<-RUBY_EVAL + acc += 1 unless cache.put_if_absent(key, key) + acc -= 1 if cache.delete_pair(key, key) + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def add_remove_to_zero_via_merge_pair(opts = {}) + code = <<-RUBY_EVAL + acc += (cache.merge_pair(key, key) {}) ? 1 : -1 + RUBY_EVAL + do_thread_loop(__method__, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys| + assert_all_key_mappings_exist(cache, keys, false) + assert_equal(cache.size, sum(result)) + end + end + + def do_thread_loop(name, code, options = {}, &block) + options = DEFAULTS.merge(options) + meth = define_loop name, code, options[:prelude] + keys = to_keys_array(options[:key_count]) + run_thread_loop(meth, keys, options, &block) + + if options[:key_count] > 1 + options[:key_count] = (options[:key_count] / 40).to_i + keys = to_hash_collision_keys_array(options[:key_count]) + run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block) + end + end + + def run_thread_loop(meth, keys, options) + cache = options[:cache_setup].call(options, keys) + barrier = ThreadSafe::Test::Barrier.new(options[:thread_count]) + result = (1..options[:thread_count]).map do + Thread.new do + setup_sync_and_start_loop(meth, cache, keys, barrier, options[:loop_count]) + end + end.map(&:value) + yield result, cache, options, keys if block_given? + end + + def setup_sync_and_start_loop(meth, cache, keys, barrier, loop_count) + my_keys = keys.shuffle + barrier.await + if my_keys.size == 1 + key = my_keys.first + send("#{meth}_single_key", cache, key, loop_count) + else + send("#{meth}_multiple_keys", cache, my_keys, loop_count) + end + end + + def define_loop(name, body, prelude) + inner_meth_name = :"_#{name}_loop_inner" + outer_meth_name = :"_#{name}_loop_outer" + # looping is splitted into the "loop methods" to trigger the JIT + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{inner_meth_name}_multiple_keys(cache, keys, i, length, acc) + #{prelude} + target = i + length + while i < target + key = keys[i] + #{body} + i += 1 + end + acc + end unless method_defined?(:#{inner_meth_name}_multiple_keys) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{inner_meth_name}_single_key(cache, key, i, length, acc) + #{prelude} + target = i + length + while i < target + #{body} + i += 1 + end + acc + end unless method_defined?(:#{inner_meth_name}_single_key) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{outer_meth_name}_multiple_keys(cache, keys, loop_count) + total_length = keys.size + acc = 0 + inc = 100 + loop_count.times do + i = 0 + pre_loop_inc = total_length % inc + acc = #{inner_meth_name}_multiple_keys(cache, keys, i, pre_loop_inc, acc) + i += pre_loop_inc + while i < total_length + acc = #{inner_meth_name}_multiple_keys(cache, keys, i, inc, acc) + i += inc + end + end + acc + end unless method_defined?(:#{outer_meth_name}_multiple_keys) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{outer_meth_name}_single_key(cache, key, loop_count) + acc = 0 + i = 0 + inc = 100 + + pre_loop_inc = loop_count % inc + acc = #{inner_meth_name}_single_key(cache, key, i, pre_loop_inc, acc) + i += pre_loop_inc + + while i < loop_count + acc = #{inner_meth_name}_single_key(cache, key, i, inc, acc) + i += inc + end + acc + end unless method_defined?(:#{outer_meth_name}_single_key) + RUBY_EVAL + outer_meth_name + end + + def to_keys_array(key_count) + arr = [] + key_count.times {|i| arr << i} + arr + end + + def to_hash_collision_keys_array(key_count) + to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)} + end + + def sum(result) + result.inject(0) {|acc, i| acc + i} + end + + def assert_standard_accumulator_test_result(result, cache, options, keys) + assert_all_key_mappings_exist(cache, keys) + assert_equal(options[:key_count], sum(result)) + assert_equal(options[:key_count], cache.size) + end + + def assert_all_key_mappings_exist(cache, keys, all_must_exist = true) + keys.each do |key| + if (value = cache[key]) || all_must_exist + assert_equal key, value unless key == value # don't do a bazzilion assertions unless necessary + end + end + end + + def assert_count_up(result, cache, options, keys) + keys.each do |key| + unless value = cache[key] + assert value + end + end + assert_equal(sum(cache.values), sum(result)) + assert_equal(options[:key_count], cache.size) + end +end unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS'] diff --git a/.gems/gems/thread_safe-0.3.4/test/test_hash.rb b/.gems/gems/thread_safe-0.3.4/test/test_hash.rb new file mode 100644 index 0000000..12f4975 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_hash.rb @@ -0,0 +1,17 @@ +require 'thread_safe' +require File.join(File.dirname(__FILE__), "test_helper") + +class TestHash < Minitest::Test + def test_concurrency + hsh = ThreadSafe::Hash.new + (1..100).map do |i| + Thread.new do + 1000.times do |j| + hsh[i*1000+j] = i + hsh[i*1000+j] + hsh.delete(i*1000+j) + end + end + end.map(&:join) + end +end diff --git a/.gems/gems/thread_safe-0.3.4/test/test_helper.rb b/.gems/gems/thread_safe-0.3.4/test/test_helper.rb new file mode 100644 index 0000000..9e49ad4 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_helper.rb @@ -0,0 +1,113 @@ +require 'thread' +require 'rubygems' +gem 'minitest', '>= 4' +require 'minitest/autorun' + +if Minitest.const_defined?('Test') + # We're on Minitest 5+. Nothing to do here. +else + # Minitest 4 doesn't have Minitest::Test yet. + Minitest::Test = MiniTest::Unit::TestCase +end + +if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE'] + # to be used like this: rake test TEST_NO_UNSAFE=true + load 'test/package.jar' + java_import 'thread_safe.SecurityManager' + manager = SecurityManager.new + + # Prevent accessing internal classes + manager.deny java.lang.RuntimePermission.new("accessClassInPackage.sun.misc") + java.lang.System.setSecurityManager manager + + class TestNoUnsafe < Minitest::Test + def test_security_manager_is_used + begin + java_import 'sun.misc.Unsafe' + flunk + rescue SecurityError + end + end + + def test_no_unsafe_version_of_chmv8_is_used + require 'thread_safe/jruby_cache_backend' # make sure the jar has been loaded + assert !Java::OrgJrubyExtThread_safe::JRubyCacheBackendLibrary::JRubyCacheBackend::CAN_USE_UNSAFE_CHM + end + end +end + +module ThreadSafe + module Test + class Latch + def initialize(count = 1) + @count = count + @mutex = Mutex.new + @cond = ConditionVariable.new + end + + def release + @mutex.synchronize do + @count -= 1 if @count > 0 + @cond.broadcast if @count.zero? + end + end + + def await + @mutex.synchronize do + @cond.wait @mutex if @count > 0 + end + end + end + + class Barrier < Latch + def await + @mutex.synchronize do + if @count.zero? # fall through + elsif @count > 0 + @count -= 1 + @count.zero? ? @cond.broadcast : @cond.wait(@mutex) + end + end + end + end + + class HashCollisionKey + attr_reader :hash, :key + def initialize(key, hash = key.hash % 3) + @key = key + @hash = hash + end + + def eql?(other) + other.kind_of?(self.class) && @key.eql?(other.key) + end + + def even? + @key.even? + end + + def <=>(other) + @key <=> other.key + end + end + + # having 4 separate HCK classes helps for a more thorough CHMV8 testing + class HashCollisionKey2 < HashCollisionKey; end + class HashCollisionKeyNoCompare < HashCollisionKey + def <=>(other) + 0 + end + end + class HashCollisionKey4 < HashCollisionKeyNoCompare; end + + HASH_COLLISION_CLASSES = [HashCollisionKey, HashCollisionKey2, HashCollisionKeyNoCompare, HashCollisionKey4] + + def self.HashCollisionKey(key, hash = key.hash % 3) + HASH_COLLISION_CLASSES[rand(4)].new(key, hash) + end + + class HashCollisionKeyNonComparable < HashCollisionKey + undef <=> + end + end +end diff --git a/.gems/gems/thread_safe-0.3.4/test/test_synchronized_delegator.rb b/.gems/gems/thread_safe-0.3.4/test/test_synchronized_delegator.rb new file mode 100644 index 0000000..40c9438 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/test/test_synchronized_delegator.rb @@ -0,0 +1,84 @@ +require 'thread_safe/synchronized_delegator.rb' +require File.join(File.dirname(__FILE__), "test_helper") + +class TestSynchronizedDelegator < Minitest::Test + + def test_wraps_array + sync_array = SynchronizedDelegator.new(array = []) + + array << 1 + assert_equal 1, sync_array[0] + + sync_array << 2 + assert_equal 2, array[1] + end + + def test_synchronizes_access + t1_continue, t2_continue = false, false + + hash = Hash.new do |hash, key| + t2_continue = true + unless hash.find { |e| e[1] == key.to_s } # just to do something + hash[key] = key.to_s + Thread.pass until t1_continue + end + end + sync_hash = SynchronizedDelegator.new(hash) + sync_hash[1] = 'egy' + + t1 = Thread.new do + sync_hash[2] = 'dva' + sync_hash[3] # triggers t2_continue + end + + t2 = Thread.new do + Thread.pass until t2_continue + sync_hash[4] = '42' + end + + sleep(0.05) # sleep some to allow threads to boot + + until t2.status == 'sleep' do + Thread.pass + end + + assert_equal 3, hash.keys.size + + t1_continue = true + t1.join; t2.join + + assert_equal 4, sync_hash.size + end + + def test_synchronizes_access_with_block + t1_continue, t2_continue = false, false + + sync_array = SynchronizedDelegator.new(array = []) + + t1 = Thread.new do + sync_array << 1 + sync_array.each do + t2_continue = true + Thread.pass until t1_continue + end + end + + t2 = Thread.new do + # sleep(0.01) + Thread.pass until t2_continue + sync_array << 2 + end + + until t2.status == 'sleep' || t2.status == false do + Thread.pass + end + + assert_equal 1, array.size + + t1_continue = true + t1.join; t2.join + + assert_equal [1, 2], array + end + +end diff --git a/.gems/gems/thread_safe-0.3.4/thread_safe.gemspec b/.gems/gems/thread_safe-0.3.4/thread_safe.gemspec new file mode 100644 index 0000000..be217a0 --- /dev/null +++ b/.gems/gems/thread_safe-0.3.4/thread_safe.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path('../lib', __FILE__) unless $:.include?('lib') +require 'thread_safe/version' + +Gem::Specification.new do |gem| + gem.authors = ["Charles Oliver Nutter", "thedarkone"] + gem.email = ["headius@headius.com", "thedarkone2@gmail.com"] + gem.description = %q{Thread-safe collections and utilities for Ruby} + gem.summary = %q{A collection of data structures and utilities to make thread-safe programming in Ruby easier} + gem.homepage = "https://github.com/headius/thread_safe" + + gem.files = `git ls-files`.split($\) + gem.files += ['lib/thread_safe/jruby_cache_backend.jar'] if defined?(JRUBY_VERSION) + gem.files -= ['.gitignore'] # see https://github.com/headius/thread_safe/issues/40#issuecomment-42315441 + gem.platform = 'java' if defined?(JRUBY_VERSION) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "thread_safe" + gem.require_paths = ["lib"] + gem.version = ThreadSafe::VERSION + gem.license = "Apache-2.0" + + gem.add_development_dependency 'atomic', ['>= 1.1.7', '< 2'] + gem.add_development_dependency 'rake' + gem.add_development_dependency 'minitest', '>= 4' +end diff --git a/.gems/specifications/addressable-2.3.6.gemspec b/.gems/specifications/addressable-2.3.6.gemspec new file mode 100644 index 0000000..55c1cbc --- /dev/null +++ b/.gems/specifications/addressable-2.3.6.gemspec @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "addressable" + s.version = "2.3.6" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Bob Aman"] + s.date = "2014-03-24" + s.description = "Addressable is a replacement for the URI implementation that is part of\nRuby's standard library. It more closely conforms to the relevant RFCs and\nadds support for IRIs and URI templates.\n" + s.email = "bob@sporkmonger.com" + s.extra_rdoc_files = ["README.md"] + s.files = ["README.md"] + s.homepage = "http://addressable.rubyforge.org/" + s.licenses = ["Apache License 2.0"] + s.rdoc_options = ["--main", "README.md"] + s.require_paths = ["lib"] + s.rubyforge_project = "addressable" + s.rubygems_version = "2.0.14" + s.summary = "URI Implementation" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 0.7.3"]) + s.add_development_dependency(%q, [">= 2.9.0"]) + s.add_development_dependency(%q, [">= 0.3.2"]) + else + s.add_dependency(%q, [">= 0.7.3"]) + s.add_dependency(%q, [">= 2.9.0"]) + s.add_dependency(%q, [">= 0.3.2"]) + end + else + s.add_dependency(%q, [">= 0.7.3"]) + s.add_dependency(%q, [">= 2.9.0"]) + s.add_dependency(%q, [">= 0.3.2"]) + end +end diff --git a/.gems/specifications/buftok-0.2.0.gemspec b/.gems/specifications/buftok-0.2.0.gemspec new file mode 100644 index 0000000..ba01c3b --- /dev/null +++ b/.gems/specifications/buftok-0.2.0.gemspec @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "buftok" + s.version = "0.2.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version= + s.authors = ["Tony Arcieri", "Martin Emde", "Erik Michaels-Ober"] + s.date = "2013-11-22" + s.description = "BufferedTokenizer extracts token delimited entities from a sequence of arbitrary inputs" + s.email = "sferik@gmail.com" + s.homepage = "https://github.com/sferik/buftok" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "BufferedTokenizer extracts token delimited entities from a sequence of arbitrary inputs" + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["~> 1.0"]) + else + s.add_dependency(%q, ["~> 1.0"]) + end + else + s.add_dependency(%q, ["~> 1.0"]) + end +end diff --git a/.gems/specifications/equalizer-0.0.9.gemspec b/.gems/specifications/equalizer-0.0.9.gemspec new file mode 100644 index 0000000..2a30b2c --- /dev/null +++ b/.gems/specifications/equalizer-0.0.9.gemspec @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "equalizer" + s.version = "0.0.9" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Dan Kubb", "Markus Schirp"] + s.date = "2013-12-23" + s.description = "Module to define equality, equivalence and inspection methods" + s.email = ["dan.kubb@gmail.com", "mbj@schirp-dso.com"] + s.extra_rdoc_files = ["LICENSE", "README.md", "CONTRIBUTING.md"] + s.files = ["LICENSE", "README.md", "CONTRIBUTING.md"] + s.homepage = "https://github.com/dkubb/equalizer" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "Module to define equality, equivalence and inspection methods" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 1.3.5", "~> 1.3"]) + else + s.add_dependency(%q, [">= 1.3.5", "~> 1.3"]) + end + else + s.add_dependency(%q, [">= 1.3.5", "~> 1.3"]) + end +end diff --git a/.gems/specifications/faraday-0.9.0.gemspec b/.gems/specifications/faraday-0.9.0.gemspec new file mode 100644 index 0000000..35b8c20 --- /dev/null +++ b/.gems/specifications/faraday-0.9.0.gemspec @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "faraday" + s.version = "0.9.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version= + s.authors = ["Rick Olson"] + s.date = "2014-01-16" + s.email = "technoweenie@gmail.com" + s.homepage = "https://github.com/lostisland/faraday" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "HTTP/REST API client library." + + if s.respond_to? :specification_version then + s.specification_version = 2 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, ["< 3", ">= 1.2"]) + s.add_development_dependency(%q, ["~> 1.0"]) + else + s.add_dependency(%q, ["< 3", ">= 1.2"]) + s.add_dependency(%q, ["~> 1.0"]) + end + else + s.add_dependency(%q, ["< 3", ">= 1.2"]) + s.add_dependency(%q, ["~> 1.0"]) + end +end diff --git a/.gems/specifications/http-0.6.2.gemspec b/.gems/specifications/http-0.6.2.gemspec new file mode 100644 index 0000000..6065dfb --- /dev/null +++ b/.gems/specifications/http-0.6.2.gemspec @@ -0,0 +1,32 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "http" + s.version = "0.6.2" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Tony", "Arcieri"] + s.date = "2014-08-06" + s.description = "An easy-to-use client library for making requests from Ruby. It uses a simple method chaining system for building requests, similar to Python's Requests." + s.email = ["tony.arcieri@gmail.com"] + s.homepage = "https://github.com/tarcieri/http" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "HTTP should be easy" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, ["~> 0.6.0"]) + s.add_development_dependency(%q, ["~> 1.0"]) + else + s.add_dependency(%q, ["~> 0.6.0"]) + s.add_dependency(%q, ["~> 1.0"]) + end + else + s.add_dependency(%q, ["~> 0.6.0"]) + s.add_dependency(%q, ["~> 1.0"]) + end +end diff --git a/.gems/specifications/http_parser.rb-0.6.0.gemspec b/.gems/specifications/http_parser.rb-0.6.0.gemspec new file mode 100644 index 0000000..0214801 --- /dev/null +++ b/.gems/specifications/http_parser.rb-0.6.0.gemspec @@ -0,0 +1,46 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "http_parser.rb" + s.version = "0.6.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Marc-Andre Cournoyer", "Aman Gupta"] + s.date = "2013-12-11" + s.description = "Ruby bindings to http://github.com/ry/http-parser and http://github.com/a2800276/http-parser.java" + s.email = ["macournoyer@gmail.com", "aman@tmm1.net"] + s.extensions = ["ext/ruby_http_parser/extconf.rb"] + s.files = ["ext/ruby_http_parser/extconf.rb"] + s.homepage = "http://github.com/tmm1/http_parser.rb" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "Simple callback-based HTTP request/response parser" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 0.7.9"]) + s.add_development_dependency(%q, [">= 2.0.1"]) + s.add_development_dependency(%q, [">= 1.4.6"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0.8.1"]) + else + s.add_dependency(%q, [">= 0.7.9"]) + s.add_dependency(%q, [">= 2.0.1"]) + s.add_dependency(%q, [">= 1.4.6"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0.8.1"]) + end + else + s.add_dependency(%q, [">= 0.7.9"]) + s.add_dependency(%q, [">= 2.0.1"]) + s.add_dependency(%q, [">= 1.4.6"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0.8.1"]) + end +end diff --git a/.gems/specifications/memoizable-0.4.2.gemspec b/.gems/specifications/memoizable-0.4.2.gemspec new file mode 100644 index 0000000..750ac88 --- /dev/null +++ b/.gems/specifications/memoizable-0.4.2.gemspec @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "memoizable" + s.version = "0.4.2" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Dan Kubb", "Erik Michaels-Ober"] + s.date = "2014-03-27" + s.description = "Memoize method return values" + s.email = ["dan.kubb@gmail.com", "sferik@gmail.com"] + s.extra_rdoc_files = ["CONTRIBUTING.md", "LICENSE.md", "README.md"] + s.files = ["CONTRIBUTING.md", "LICENSE.md", "README.md"] + s.homepage = "https://github.com/dkubb/memoizable" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "Memoize method return values" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 0.3.1", "~> 0.3"]) + s.add_development_dependency(%q, [">= 1.5.3", "~> 1.5"]) + else + s.add_dependency(%q, [">= 0.3.1", "~> 0.3"]) + s.add_dependency(%q, [">= 1.5.3", "~> 1.5"]) + end + else + s.add_dependency(%q, [">= 0.3.1", "~> 0.3"]) + s.add_dependency(%q, [">= 1.5.3", "~> 1.5"]) + end +end diff --git a/.gems/specifications/multipart-post-2.0.0.gemspec b/.gems/specifications/multipart-post-2.0.0.gemspec new file mode 100644 index 0000000..21bff89 --- /dev/null +++ b/.gems/specifications/multipart-post-2.0.0.gemspec @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "multipart-post" + s.version = "2.0.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Nick Sieger"] + s.date = "2013-12-21" + s.description = "Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file." + s.email = ["nick@nicksieger.com"] + s.homepage = "https://github.com/nicksieger/multipart-post" + s.licenses = ["MIT"] + s.rdoc_options = ["--main", "README.md", "-SHN", "-f", "darkfish"] + s.require_paths = ["lib"] + s.rubyforge_project = "caldersphere" + s.rubygems_version = "2.0.14" + s.summary = "A multipart form post accessory for Net::HTTP." +end diff --git a/.gems/specifications/naught-1.0.0.gemspec b/.gems/specifications/naught-1.0.0.gemspec new file mode 100644 index 0000000..3ceec69 --- /dev/null +++ b/.gems/specifications/naught-1.0.0.gemspec @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "naught" + s.version = "1.0.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Avdi Grimm"] + s.date = "2014-01-26" + s.description = "Naught is a toolkit for building Null Objects" + s.email = ["avdi@avdi.org"] + s.homepage = "https://github.com/avdi/naught" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "Naught is a toolkit for building Null Objects" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["~> 1.3"]) + else + s.add_dependency(%q, ["~> 1.3"]) + end + else + s.add_dependency(%q, ["~> 1.3"]) + end +end diff --git a/.gems/specifications/simple_oauth-0.2.0.gemspec b/.gems/specifications/simple_oauth-0.2.0.gemspec new file mode 100644 index 0000000..ce1a411 --- /dev/null +++ b/.gems/specifications/simple_oauth-0.2.0.gemspec @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "simple_oauth" + s.version = "0.2.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Steve Richert", "Erik Michaels-Ober"] + s.date = "2012-12-02" + s.description = "Simply builds and verifies OAuth headers" + s.email = ["steve.richert@gmail.com", "sferik@gmail.com"] + s.homepage = "https://github.com/laserlemon/simple_oauth" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "Simply builds and verifies OAuth headers" + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 2"]) + s.add_development_dependency(%q, [">= 0"]) + else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 2"]) + s.add_dependency(%q, [">= 0"]) + end + else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 2"]) + s.add_dependency(%q, [">= 0"]) + end +end diff --git a/.gems/specifications/thread_safe-0.3.4.gemspec b/.gems/specifications/thread_safe-0.3.4.gemspec new file mode 100644 index 0000000..9883cc4 --- /dev/null +++ b/.gems/specifications/thread_safe-0.3.4.gemspec @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "thread_safe" + s.version = "0.3.4" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Charles Oliver Nutter", "thedarkone"] + s.date = "2014-05-27" + s.description = "Thread-safe collections and utilities for Ruby" + s.email = ["headius@headius.com", "thedarkone2@gmail.com"] + s.homepage = "https://github.com/headius/thread_safe" + s.licenses = ["Apache-2.0"] + s.require_paths = ["lib"] + s.rubygems_version = "2.0.14" + s.summary = "A collection of data structures and utilities to make thread-safe programming in Ruby easier" + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, ["< 2", ">= 1.1.7"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 4"]) + else + s.add_dependency(%q, ["< 2", ">= 1.1.7"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 4"]) + end + else + s.add_dependency(%q, ["< 2", ">= 1.1.7"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 4"]) + end +end diff --git a/.gems/specifications/twitter-5.11.0.gemspec b/.gems/specifications/twitter-5.11.0.gemspec index dcad08f..fbf9260 100644 --- a/.gems/specifications/twitter-5.11.0.gemspec +++ b/.gems/specifications/twitter-5.11.0.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.homepage = "http://sferik.github.com/twitter/" s.licenses = ["MIT"] s.require_paths = ["lib"] - s.rubygems_version = "1.8.23" + s.rubygems_version = "2.0.14" s.summary = "A Ruby interface to the Twitter API." if s.respond_to? :specification_version then