hobix 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. data/COPYING +18 -0
  2. data/README +18 -0
  3. data/Rakefile +96 -0
  4. data/bin/hobix +94 -0
  5. data/contrib/blosxom-to-hobix.rb +253 -0
  6. data/contrib/txp-to-hobix.rb +56 -0
  7. data/contrib/webrick-all-mine.rb +20 -0
  8. data/doc/CHANGELOG +285 -0
  9. data/doc/rdoc/classes/Hobix/API.html +382 -0
  10. data/doc/rdoc/classes/Hobix/Article.html +111 -0
  11. data/doc/rdoc/classes/Hobix/BaseContent.html +692 -0
  12. data/doc/rdoc/classes/Hobix/BaseEntry.html +218 -0
  13. data/doc/rdoc/classes/Hobix/BaseFacet.html +205 -0
  14. data/doc/rdoc/classes/Hobix/BaseOutput.html +122 -0
  15. data/doc/rdoc/classes/Hobix/BasePlugin.html +201 -0
  16. data/doc/rdoc/classes/Hobix/BaseProperties/ClassMethods.html +243 -0
  17. data/doc/rdoc/classes/Hobix/BaseProperties.html +218 -0
  18. data/doc/rdoc/classes/Hobix/BasePublish.html +157 -0
  19. data/doc/rdoc/classes/Hobix/BaseStorage.html +417 -0
  20. data/doc/rdoc/classes/Hobix/BixWik/Entry.html +196 -0
  21. data/doc/rdoc/classes/Hobix/BixWik/IndexEntry.html +170 -0
  22. data/doc/rdoc/classes/Hobix/BixWik/WikiRedCloth.html +111 -0
  23. data/doc/rdoc/classes/Hobix/BixWik.html +418 -0
  24. data/doc/rdoc/classes/Hobix/BixWikPlugin.html +158 -0
  25. data/doc/rdoc/classes/Hobix/CommandLine.html +1970 -0
  26. data/doc/rdoc/classes/Hobix/Comment.html +113 -0
  27. data/doc/rdoc/classes/Hobix/Config.html +212 -0
  28. data/doc/rdoc/classes/Hobix/DataMarsh.html +667 -0
  29. data/doc/rdoc/classes/Hobix/Entry.html +178 -0
  30. data/doc/rdoc/classes/Hobix/EntryEnum.html +162 -0
  31. data/doc/rdoc/classes/Hobix/Enumerable.html +170 -0
  32. data/doc/rdoc/classes/Hobix/Facets/WikiEdit.html +180 -0
  33. data/doc/rdoc/classes/Hobix/Facets.html +111 -0
  34. data/doc/rdoc/classes/Hobix/LinkList.html +182 -0
  35. data/doc/rdoc/classes/Hobix/Out/Quick.html +412 -0
  36. data/doc/rdoc/classes/Hobix/Out.html +119 -0
  37. data/doc/rdoc/classes/Hobix/Page.html +381 -0
  38. data/doc/rdoc/classes/Hobix/Trackback.html +113 -0
  39. data/doc/rdoc/classes/Hobix/UriStr.html +198 -0
  40. data/doc/rdoc/classes/Hobix/WebApp/QueryString.html +207 -0
  41. data/doc/rdoc/classes/Hobix/WebApp/QueryValidationFailure.html +111 -0
  42. data/doc/rdoc/classes/Hobix/WebApp.html +1383 -0
  43. data/doc/rdoc/classes/Hobix/Weblog/AuthorNotFound.html +111 -0
  44. data/doc/rdoc/classes/Hobix/Weblog.html +2082 -0
  45. data/doc/rdoc/classes/Hobix.html +399 -0
  46. data/doc/rdoc/classes/Kernel.html +139 -0
  47. data/doc/rdoc/classes/Regexp.html +154 -0
  48. data/doc/rdoc/classes/YAML/Omap.html +144 -0
  49. data/doc/rdoc/classes/YAML.html +111 -0
  50. data/doc/rdoc/created.rid +1 -0
  51. data/doc/rdoc/files/COPYING.html +129 -0
  52. data/doc/rdoc/files/README.html +131 -0
  53. data/doc/rdoc/files/doc/CHANGELOG.html +101 -0
  54. data/doc/rdoc/files/lib/hobix/api_rb.html +119 -0
  55. data/doc/rdoc/files/lib/hobix/article_rb.html +126 -0
  56. data/doc/rdoc/files/lib/hobix/base_rb.html +128 -0
  57. data/doc/rdoc/files/lib/hobix/bixwik_rb.html +126 -0
  58. data/doc/rdoc/files/lib/hobix/commandline_rb.html +140 -0
  59. data/doc/rdoc/files/lib/hobix/comments_rb.html +126 -0
  60. data/doc/rdoc/files/lib/hobix/config_rb.html +125 -0
  61. data/doc/rdoc/files/lib/hobix/datamarsh_rb.html +108 -0
  62. data/doc/rdoc/files/lib/hobix/entry_rb.html +118 -0
  63. data/doc/rdoc/files/lib/hobix/linklist_rb.html +127 -0
  64. data/doc/rdoc/files/lib/hobix/publisher_rb.html +126 -0
  65. data/doc/rdoc/files/lib/hobix/trackbacks_rb.html +128 -0
  66. data/doc/rdoc/files/lib/hobix/webapp_rb.html +127 -0
  67. data/doc/rdoc/files/lib/hobix/weblog_rb.html +135 -0
  68. data/doc/rdoc/files/lib/hobix_rb.html +127 -0
  69. data/doc/rdoc/fr_class_index.html +67 -0
  70. data/doc/rdoc/fr_file_index.html +44 -0
  71. data/doc/rdoc/fr_method_index.html +307 -0
  72. data/doc/rdoc/index.html +24 -0
  73. data/doc/rdoc/rdoc-style.css +208 -0
  74. data/git_hobix_update.php +13 -0
  75. data/lib/hobix/api.rb +91 -0
  76. data/lib/hobix/article.rb +22 -0
  77. data/lib/hobix/base.rb +480 -0
  78. data/lib/hobix/bixwik.rb +200 -0
  79. data/lib/hobix/commandline.rb +677 -0
  80. data/lib/hobix/comments.rb +98 -0
  81. data/lib/hobix/config.rb +39 -0
  82. data/lib/hobix/datamarsh.rb +110 -0
  83. data/lib/hobix/entry.rb +84 -0
  84. data/lib/hobix/facets/comments.rb +99 -0
  85. data/lib/hobix/facets/publisher.rb +314 -0
  86. data/lib/hobix/facets/trackbacks.rb +80 -0
  87. data/lib/hobix/linklist.rb +81 -0
  88. data/lib/hobix/out/atom.rb +101 -0
  89. data/lib/hobix/out/erb.rb +64 -0
  90. data/lib/hobix/out/okaynews.rb +55 -0
  91. data/lib/hobix/out/quick.rb +314 -0
  92. data/lib/hobix/out/rdf.rb +97 -0
  93. data/lib/hobix/out/redrum.rb +26 -0
  94. data/lib/hobix/out/rss.rb +128 -0
  95. data/lib/hobix/plugin/akismet.rb +196 -0
  96. data/lib/hobix/plugin/bloglines.rb +73 -0
  97. data/lib/hobix/plugin/calendar.rb +212 -0
  98. data/lib/hobix/plugin/flickr.rb +110 -0
  99. data/lib/hobix/plugin/recent_comments.rb +84 -0
  100. data/lib/hobix/plugin/sections.rb +91 -0
  101. data/lib/hobix/plugin/tags.rb +60 -0
  102. data/lib/hobix/publish/ping.rb +53 -0
  103. data/lib/hobix/publish/replicate.rb +283 -0
  104. data/lib/hobix/publisher.rb +18 -0
  105. data/lib/hobix/search/dictionary.rb +141 -0
  106. data/lib/hobix/search/porter_stemmer.rb +203 -0
  107. data/lib/hobix/search/simple.rb +209 -0
  108. data/lib/hobix/search/vector.rb +100 -0
  109. data/lib/hobix/storage/filesys.rb +408 -0
  110. data/lib/hobix/trackbacks.rb +93 -0
  111. data/lib/hobix/util/objedit.rb +193 -0
  112. data/lib/hobix/util/patcher.rb +155 -0
  113. data/lib/hobix/webapp/cli.rb +195 -0
  114. data/lib/hobix/webapp/htmlform.rb +107 -0
  115. data/lib/hobix/webapp/message.rb +177 -0
  116. data/lib/hobix/webapp/urigen.rb +141 -0
  117. data/lib/hobix/webapp/webrick-servlet.rb +90 -0
  118. data/lib/hobix/webapp.rb +723 -0
  119. data/lib/hobix/weblog.rb +893 -0
  120. data/lib/hobix.rb +230 -0
  121. data/share/default-blog/hobix.yaml +16 -0
  122. data/share/default-blog/htdocs/site.css +174 -0
  123. data/share/default-blog/skel/entry.html.quick +0 -0
  124. data/share/default-blog/skel/index.atom.atom +0 -0
  125. data/share/default-blog/skel/index.html.quick-summary +0 -0
  126. data/share/default-blog/skel/index.xml.rss +0 -0
  127. data/share/default-blog/skel/index.yaml.okaynews +0 -0
  128. data/share/default-blog/skel/monthly.html.quick-archive +0 -0
  129. data/share/default-blog/skel/section.html.quick-archive +0 -0
  130. data/share/default-blog/skel/yearly.html.quick-archive +0 -0
  131. data/share/default-blog-modes.yaml +7 -0
  132. data/share/default-blog.apache-cgi.patch +8 -0
  133. data/share/default-blog.apache-ssi.patch +38 -0
  134. data/share/default-blog.apache2-ssi.patch +3 -0
  135. data/share/default-blog.cgi.patch +8 -0
  136. data/share/default-blog.comments.patch +5 -0
  137. data/share/default-blog.prototype.patch +766 -0
  138. data/share/default-blog.publisher.patch +5 -0
  139. data/share/default-blog.wiki.patch +29 -0
  140. data/share/publisher/css/control.css +90 -0
  141. data/share/publisher/css/form.css +238 -0
  142. data/share/publisher/css/form.import.css +72 -0
  143. data/share/publisher/css/main-menu.css +134 -0
  144. data/share/publisher/i/hobix-emblazen-1.png +0 -0
  145. data/share/publisher/i/hobix-emblazen-2.png +0 -0
  146. data/share/publisher/i/hobix-emblazen-3.png +0 -0
  147. data/share/publisher/i/hobix-emblazen-4.png +0 -0
  148. data/share/publisher/i/hobix-emblazen-5.png +0 -0
  149. data/share/publisher/i/hobix-emblazen-6.png +0 -0
  150. data/share/publisher/i/hobix-emblazen-7.png +0 -0
  151. data/share/publisher/index.erb +66 -0
  152. data/share/publisher/js/controls.js +261 -0
  153. data/share/publisher/js/dragdrop.js +476 -0
  154. data/share/publisher/js/effects.js +570 -0
  155. data/share/publisher/js/prototype.js +1011 -0
  156. metadata +230 -0
@@ -0,0 +1,155 @@
1
+ #
2
+ # = hobix/util/patcher
3
+ #
4
+ # Hobix command-line weblog system.
5
+ #
6
+ # Copyright (c) 2003-2004 why the lucky stiff
7
+ #
8
+ # Written & maintained by why the lucky stiff <why@ruby-lang.org>
9
+ #
10
+ # This program is free software, released under a BSD license.
11
+ # See COPYING for details.
12
+ #
13
+ #--
14
+ # $Id$
15
+ #++
16
+ require 'fileutils'
17
+
18
+ module Hobix
19
+ module Util
20
+ # The Patcher class applies Hobix's own YAML patch format to a directory.
21
+ # These patches can create or append to existing plain-text files, as well
22
+ # as modifying YAML files using YPath.
23
+ #
24
+ # To apply your patch:
25
+ #
26
+ # patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
27
+ # patch_set.apply('/dir/to/unaltered/code')
28
+ #
29
+ class PatchError < Exception; end
30
+ class Patcher
31
+ # Initialize the Patcher with a list of +paths+ to patches which
32
+ # must be applied in order.
33
+ #
34
+ # patch_set = Hobix::Util::Patcher.new('1.patch', '2.patch')
35
+ # patch_set.apply('/dir/to/unaltered/code')
36
+ #
37
+ def initialize( *paths )
38
+ @patches = {}
39
+ paths.each do |path|
40
+ YAML::load_file( path ).each do |k, v|
41
+ ( @patches[k] ||= [] ) << v
42
+ end
43
+ end
44
+ end
45
+
46
+ # Alias for Patcher.new.
47
+ #
48
+ # patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
49
+ # patch_set.apply('/dir/to/unaltered/code')
50
+ #
51
+ def Patcher.[]( *paths )
52
+ Patcher.new( *paths )
53
+ end
54
+
55
+ # Apply the patches loaded into this class against a +path+ containing
56
+ # unaltered files.
57
+ #
58
+ # patch_set = Hobix::Util::Patcher['1.patch', '2.patch']
59
+ # patch_set.apply('/dir/to/unaltered/code')
60
+ #
61
+ def apply( path )
62
+ @patches.map do |fname, patchset|
63
+ fname = File.join( path, fname ) # .gsub( /^.*?[\/\\]/, '' ) )
64
+ ftarg = File.read( fname ) rescue ''
65
+ ftarg = YAML::load( ftarg ) if fname =~ /\.yaml$/
66
+
67
+ patchset.each_with_index do |(ptype, patch), patchno|
68
+ # apply the changes
69
+ puts "*** Applying patch ##{ patchno + 1 } for #{ fname } (#{ ptype })."
70
+ ftarg = method( ptype.gsub( /\W/, '_' ) ).call( ftarg, patch )
71
+ end
72
+
73
+ [fname, ftarg]
74
+ end.
75
+ each do |fname, ftext|
76
+ # save the files
77
+ if ftext == :remove
78
+ FileUtils.rm_rf fname
79
+ else
80
+ FileUtils.makedirs( File.dirname( fname ) )
81
+ ftext = ftext.to_yaml if fname =~ /\.yaml$/
82
+ File.open( fname, 'w+' ) { |f| f << ftext }
83
+ end
84
+ end
85
+ end
86
+
87
+ def file_remove( target, text )
88
+ :remove
89
+ end
90
+
91
+ def file_create( target, text )
92
+ text.to_s
93
+ end
94
+
95
+ def file_ensure( target, text )
96
+ target << text unless target.include? text
97
+ target
98
+ end
99
+
100
+ def yaml_merge( obj, merge )
101
+ obj = obj.value if obj.respond_to? :value
102
+ if obj.class != merge.class and merge.class != Hash
103
+ raise PatchError, "*** Patch failure since #{ obj.class } != #{ merge.class }."
104
+ end
105
+
106
+ case obj
107
+ when Hash
108
+ merge.each do |k, v|
109
+ if obj.has_key? k
110
+ yaml_merge obj[k], v
111
+ else
112
+ obj[k] = v
113
+ end
114
+ end
115
+ when Array
116
+ at = nil
117
+ merge.each do |v|
118
+ vat = obj.index( v )
119
+ if vat
120
+ at = vat if vat > at.to_i
121
+ else
122
+ if at
123
+ obj[at+=1,0] = v
124
+ else
125
+ obj << v
126
+ end
127
+ end
128
+ end
129
+ when String
130
+ obj.replace merge
131
+ else
132
+ merge.each do |k, v|
133
+ ivar = obj.instance_variable_get "@#{k}"
134
+ if ivar
135
+ yaml_merge ivar, v
136
+ else
137
+ obj.instance_variable_set "@#{k}", v
138
+ end
139
+ end
140
+ end
141
+
142
+ obj
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ YAML::add_domain_type( 'hobix.com,2004', 'patches/list' ) do |type, val|
149
+ val
150
+ end
151
+ ['yaml-merge', 'file-create', 'file-ensure', 'file-remove'].each do |ptype|
152
+ YAML::add_domain_type( 'hobix.com,2004', 'patches/' + ptype ) do |type, val|
153
+ [ptype, val]
154
+ end
155
+ end
@@ -0,0 +1,195 @@
1
+ # = webapp command line interface
2
+ #
3
+ # A web application using webapp has CLI (command line interface).
4
+ # You can invoke a webapp script from command line.
5
+ #
6
+ # xxx.cgi [options] [/path_info] [?query_string]
7
+ # -h, --help show this message
8
+ # -o, --output=FILE set output file
9
+ # --cern-meta output header as CERN httpd metafile
10
+ # --server-name=STRING set server name
11
+ # --server-port=INTEGER set server port number
12
+ # --script-name=STRING set script name
13
+ # --remote-addr=STRING set remote IP address
14
+ # --header=NAME:BODY set additional request header
15
+ #
16
+ # For example, hello.cgi, as follows, can be invoked from command line.
17
+ #
18
+ # % cat hello.cgi
19
+ # #!/usr/bin/env ruby
20
+ # require 'webapp'
21
+ # WebApp {|w| w.puts "Hello" }
22
+ # % ./hello.cgi
23
+ # Status: 200 OK
24
+ # Content-Type: text/plain
25
+ # Content-Length: 6
26
+ #
27
+ # Hello
28
+ #
29
+ # webapp.rb can be used in command line directly as follows.
30
+ # This document use the form to make examples short.
31
+ #
32
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }'
33
+ # Status: 200 OK
34
+ # Content-Type: text/plain
35
+ # Content-Length: 6
36
+ #
37
+ # Hello
38
+ #
39
+ # The web application takes two optional argument: path info and query string.
40
+ # The optional first argument which begins with '/' is path info.
41
+ # The optional second argument which begins with '?' is query string.
42
+ # Since '?' is a shell meta character, it should be quoted.
43
+ #
44
+ # % ruby -rwebapp -e '
45
+ # WebApp {|w|
46
+ # w.puts w.path_info
47
+ # w.puts w.query_string
48
+ # }' /a '?q'
49
+ # Status: 200 OK
50
+ # Content-Type: text/plain
51
+ # Content-Length: 30
52
+ #
53
+ # /a
54
+ # #<WebApp::QueryString: ?q>
55
+ #
56
+ # If the option -o is specified, a response is generated on the specified file.
57
+ # Note that the format is suitable for Apache mod_asis.
58
+ #
59
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }' -- -o ~/public_html/hello.asis
60
+ # % cat ~/public_html/hello.asis
61
+ # Status: 200 OK
62
+ # Content-Type: text/plain
63
+ # Content-Length: 6
64
+ #
65
+ # Hello
66
+ #
67
+ # If the option --cern-meta is specified addition to -o,
68
+ # The header in the response is stored in separated file.
69
+ # Note that the format is suitable for Apache mod_cern_meta.
70
+ #
71
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello" }' -- --cern-meta -o ~/public_html/hello2.txt
72
+ # % cat ~/public_html/.web/hello2.txt.meta
73
+ # Content-Type: text/plain
74
+ # Content-Length: 6
75
+ # % cat ~/public_html/hello2.txt
76
+ # Hello
77
+ #
78
+ # The options --server-name, --server-port, --script-name and --remote-addr specifies
79
+ # information visible from web application.
80
+ # For example, WebApp#server_name returns a server name specified by --server-name.
81
+ #
82
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts w.server_name }'
83
+ # Status: 200 OK
84
+ # Content-Type: text/plain
85
+ # Content-Length: 10
86
+ #
87
+ # localhost
88
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts w.server_name }' -- --server-name=www.example.org
89
+ # Status: 200 OK
90
+ # Content-Type: text/plain
91
+ # Content-Length: 16
92
+ #
93
+ # www.example.org
94
+ #
95
+ # The option --header specifies an additional request header.
96
+ # For example, specifying "Accept-Encoding: gzip" makes output gzipped.
97
+ #
98
+ # % ruby -rwebapp -e 'WebApp {|w| w.puts "Hello"*100 }' -- --header='Accept-Encoding: gzip'|cat -v
99
+ # Status: 200 OK
100
+ # Content-Type: text/plain
101
+ # Content-Encoding: gzip
102
+ # Content-Length: 31
103
+ #
104
+ # ^_M-^K^H^@^O^VM-TA^@^CM-sHM-MM-IM-IM-w^X%F^RM-A^E^@ZTsDM-u^A^@^@
105
+
106
+ require 'optparse'
107
+
108
+ module Hobix
109
+ class WebApp
110
+ class Manager
111
+ # CLI (command line interface)
112
+ def run_cli
113
+ opt_output = '-'
114
+ opt_cern_meta = false
115
+ opt_server_name = 'localhost'
116
+ opt_server_port = 80
117
+ opt_script_name = "/#{File.basename($0)}"
118
+ opt_remote_addr = '127.0.0.1'
119
+ opt_headers = []
120
+ ARGV.options {|q|
121
+ q.banner = "#{File.basename $0} [options] [/path_info] [?query_string]"
122
+ q.def_option('-h', '--help', 'show this message') { puts q; exit(0) }
123
+ q.def_option('-o FILE', '--output=FILE', 'set output file') {|arg| opt_output = arg.untaint }
124
+ q.def_option('--cern-meta', 'output header as CERN httpd metafile') { opt_cern_meta = true }
125
+ q.def_option('--server-name=STRING', 'set server name') {|arg| opt_server_name = arg }
126
+ q.def_option('--server-port=INTEGER', 'set server port number') {|arg| opt_server_port = arg.to_i }
127
+ q.def_option('--script-name=STRING', 'set script name') {|arg| opt_script_name = arg }
128
+ q.def_option('--remote-addr=STRING', 'set remote IP address') {|arg| opt_remote_addr = arg }
129
+ q.def_option('--header=NAME:BODY', 'set additional request header') {|arg| opt_headers << arg.split(/:/, 2) }
130
+ q.parse!
131
+ }
132
+ if path_info = ARGV.shift
133
+ if %r{\A/} !~ path_info
134
+ ARGV.unshift path_info
135
+ path_info = nil
136
+ end
137
+ end
138
+ if query_string = ARGV.shift
139
+ if %r{\A\?} !~ query_string
140
+ ARGV.unshift query_string
141
+ query_string = nil
142
+ end
143
+ end
144
+ if !ARGV.empty?
145
+ raise "extra arguments: #{ARGV.inspect[1..-2]}"
146
+ end
147
+ path_info ||= ''
148
+ query_string ||= ''
149
+ setup_request = lambda {|req|
150
+ req.make_request_header_from_cgi_env({
151
+ 'REQUEST_METHOD' => 'GET',
152
+ 'SERVER_NAME' => opt_server_name,
153
+ 'SERVER_PORT' => opt_server_port,
154
+ 'SCRIPT_NAME' => opt_script_name,
155
+ 'PATH_INFO' => path_info,
156
+ 'QUERY_STRING' => query_string,
157
+ 'SERVER_PROTOCOL' => 'HTTP/1.0',
158
+ 'REMOTE_ADDR' => opt_remote_addr,
159
+ 'CONTENT_TYPE' => ''
160
+ })
161
+ opt_headers.each {|name, body|
162
+ req.header_object.add name, body
163
+ }
164
+ }
165
+ output_response = lambda {|res|
166
+ if opt_output == '-'
167
+ res.output_cgi_status_field($stdout)
168
+ res.output_message($stdout)
169
+ else
170
+ if opt_cern_meta
171
+ dir = "#{File.dirname(opt_output)}/.web"
172
+ begin
173
+ Dir.mkdir dir
174
+ rescue Errno::EEXIST
175
+ end
176
+ open("#{dir}/#{File.basename(opt_output)}.meta", 'w') {|f|
177
+ #res.output_cgi_status_field(f)
178
+ res.output_header(f)
179
+ }
180
+ open(opt_output, 'w') {|f|
181
+ res.output_body(f)
182
+ }
183
+ else
184
+ open(opt_output, 'w') {|f|
185
+ res.output_cgi_status_field(f)
186
+ res.output_message(f)
187
+ }
188
+ end
189
+ end
190
+ }
191
+ primitive_run(setup_request, output_response)
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,107 @@
1
+ module Hobix
2
+ class WebApp
3
+ class QueryString
4
+ # decode self as application/x-www-form-urlencoded and returns
5
+ # HTMLFormQuery object.
6
+ def decode_as_application_x_www_form_urlencoded
7
+ # xxx: warning if invalid?
8
+ pairs = []
9
+ @escaped_query_string.scan(/([^&;=]*)=([^&;]*)/) {|key, val|
10
+ key.gsub!(/\+/, ' ')
11
+ key.gsub!(/%([0-9A-F][0-9A-F])/i) { [$1].pack("H*") }
12
+ val.gsub!(/\+/, ' ')
13
+ val.gsub!(/%([0-9A-F][0-9A-F])/i) { [$1].pack("H*") }
14
+ pairs << [key.freeze, val.freeze]
15
+ }
16
+ HTMLFormQuery.new(pairs)
17
+ end
18
+ # decode self as multipart/form-data and returns
19
+ # HTMLFormQuery object.
20
+ def decode_as_multipart_form_data( boundary )
21
+ # xxx: warning if invalid?
22
+ require 'tempfile'
23
+ pairs = []
24
+ boundary = "--" + boundary
25
+ eol = "\015\012"
26
+ str = @escaped_query_string.gsub( /(?:\r?\n|\A)#{ Regexp::quote( boundary ) }--#{ eol }.*/m, '' )
27
+ str.split( /(?:\r?\n|\A)#{ Regexp::quote( boundary ) }#{ eol }/m ).each do |part|
28
+ headers = {}
29
+ header, value = part.split( "#{eol}#{eol}", 2 )
30
+ next unless header and value
31
+ field_name, field_data = nil, {}
32
+ if header =~ /Content-Disposition: form-data;.*(?:\sname="([^"]+)")/m
33
+ field_name = $1
34
+ end
35
+ if header =~ /Content-Disposition: form-data;.*(?:\sfilename="([^"]+)")/m
36
+ body = Tempfile.new( "WebApp" )
37
+ body.binmode if defined? body.binmode
38
+ body.print value
39
+ body.rewind
40
+ field_data = {'filename' => $1, 'tempfile' => body}
41
+ field_data['type'] = $1 if header =~ /Content-Type: (.+?)(?:#{ eol }|\Z)/m
42
+ else
43
+ field_data = value.gsub( /#{ eol }\Z/, '' )
44
+ end
45
+ pairs << [field_name, field_data]
46
+ end
47
+ HTMLFormQuery.new(pairs)
48
+ end
49
+ end
50
+
51
+ # HTMLFormQuery represents a query submitted by HTML form.
52
+ class HTMLFormQuery
53
+
54
+ def HTMLFormQuery.each_string_key_pair(arg, &block) # :nodoc:
55
+ if arg.respond_to? :to_ary
56
+ arg = arg.to_ary
57
+ if arg.length == 2 && arg.first.respond_to?(:to_str)
58
+ yield WebApp.make_frozen_string(arg.first), arg.last
59
+ else
60
+ arg.each {|elt|
61
+ HTMLFormQuery.each_string_key_pair(elt, &block)
62
+ }
63
+ end
64
+ elsif arg.respond_to? :to_pair
65
+ arg.each_pair {|key, val|
66
+ yield WebApp.make_frozen_string(key), val
67
+ }
68
+ else
69
+ raise ArgumentError, "non-pairs argument: #{arg.inspect}"
70
+ end
71
+ end
72
+
73
+ def initialize(*args)
74
+ @param = []
75
+ HTMLFormQuery.each_string_key_pair(args) {|key, val|
76
+ @param << [key, val]
77
+ }
78
+ @param.freeze
79
+ end
80
+
81
+ def each
82
+ @param.each {|key, val|
83
+ yield key.dup, val.dup
84
+ }
85
+ end
86
+
87
+ def [](key)
88
+ if pair = @param.assoc(key)
89
+ return pair.last.dup
90
+ end
91
+ return nil
92
+ end
93
+
94
+ def lookup_all(key)
95
+ result = []
96
+ @param.each {|k, val|
97
+ result << val if k == key
98
+ }
99
+ return result
100
+ end
101
+
102
+ def keys
103
+ @param.map {|key, val| key }.uniq
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,177 @@
1
+ module Hobix
2
+ class WebApp
3
+ # :stopdoc:
4
+ class Header
5
+ def Header.capitalize_field_name(field_name)
6
+ field_name.gsub(/[A-Za-z]+/) {|s| s.capitalize }
7
+ end
8
+
9
+ def initialize
10
+ @fields = []
11
+ end
12
+
13
+ def freeze
14
+ @fields.freeze
15
+ super
16
+ end
17
+
18
+ def dup
19
+ result = Header.new
20
+ @fields.each {|_, k, v|
21
+ result.add(k, v)
22
+ }
23
+ result
24
+ end
25
+
26
+ def clear
27
+ @fields.clear
28
+ end
29
+
30
+ def remove(field_name)
31
+ k1 = field_name.downcase
32
+ @fields.reject! {|k2, _, _| k1 == k2 }
33
+ nil
34
+ end
35
+
36
+ def add(field_name, field_body)
37
+ field_name = WebApp.make_frozen_string(field_name)
38
+ field_body = WebApp.make_frozen_string(field_body)
39
+ @fields << [field_name.downcase.freeze, field_name, field_body]
40
+ end
41
+
42
+ def set(field_name, field_body)
43
+ field_name = WebApp.make_frozen_string(field_name)
44
+ remove(field_name)
45
+ add(field_name, field_body)
46
+ end
47
+
48
+ def has?(field_name)
49
+ @fields.assoc(field_name.downcase) != nil
50
+ end
51
+
52
+ def [](field_name)
53
+ k1 = field_name.downcase
54
+ @fields.each {|k2, field_name, field_body|
55
+ return field_body.dup if k1 == k2
56
+ }
57
+ nil
58
+ end
59
+
60
+ def lookup_all(field_name)
61
+ k1 = field_name.downcase
62
+ result = []
63
+ @fields.each {|k2, field_name, field_body|
64
+ result << field_body.dup if k1 == k2
65
+ }
66
+ result
67
+ end
68
+
69
+ def each
70
+ @fields.each {|_, field_name, field_body|
71
+ field_name = field_name.dup
72
+ field_body = field_body.dup
73
+ yield field_name, field_body
74
+ }
75
+ end
76
+ end
77
+
78
+ class Message
79
+ def initialize(header={}, body='')
80
+ @header_object = Header.new
81
+ case header
82
+ when Hash
83
+ header.each_pair {|k, v|
84
+ raise ArgumentError, "unexpected header field name: #{k.inspect}" unless k.respond_to? :to_str
85
+ raise ArgumentError, "unexpected header field body: #{v.inspect}" unless v.respond_to? :to_str
86
+ @header_object.add k.to_str, v.to_str
87
+ }
88
+ when Array
89
+ header.each {|k, v|
90
+ raise ArgumentError, "unexpected header field name: #{k.inspect}" unless k.respond_to? :to_str
91
+ raise ArgumentError, "unexpected header field body: #{v.inspect}" unless v.respond_to? :to_str
92
+ @header_object.add k.to_str, v.to_str
93
+ }
94
+ else
95
+ raise ArgumentError, "unexpected header argument: #{header.inspect}"
96
+ end
97
+ raise ArgumentError, "unexpected body: #{body.inspect}" unless body.respond_to? :to_str
98
+ @body_object = StringIO.new(body.to_str)
99
+ end
100
+ attr_reader :header_object, :body_object
101
+
102
+ def freeze
103
+ @header_object.freeze
104
+ @body_object.string.freeze
105
+ super
106
+ end
107
+
108
+ def output_header(out)
109
+ @header_object.each {|k, v|
110
+ out << "#{k}: #{v}\n"
111
+ }
112
+ end
113
+
114
+ def output_body(out)
115
+ out << @body_object.string
116
+ end
117
+
118
+ def output_message(out)
119
+ output_header(out)
120
+ out << "\n"
121
+ output_body(out)
122
+ end
123
+ end
124
+
125
+ class Request < Message
126
+ def initialize(request_line=nil, header={}, body='')
127
+ @request_line = request_line
128
+ super header, body
129
+ end
130
+ attr_reader :request_method,
131
+ :server_name, :server_port,
132
+ :script_name, :path_info,
133
+ :query_string,
134
+ :server_protocol,
135
+ :remote_addr, :content_type, :request_uri, :action_uri
136
+
137
+ def make_request_header_from_cgi_env(env)
138
+ env.each {|k, v|
139
+ next if /\AHTTP_/ !~ k
140
+ k = Header.capitalize_field_name($')
141
+ k.gsub!(/_/, '-')
142
+ @header_object.add k, v
143
+ }
144
+ @request_method = env['REQUEST_METHOD']
145
+ @server_name = ( env['SERVER_NAME'] || '' ).gsub( /\:\d+$/, '' ) # lighttpd affixes port!!
146
+ @server_port = env['SERVER_PORT'].to_i
147
+ @script_name = env['SCRIPT_NAME'] || ''
148
+ @path_info = env['PATH_INFO'] || ''
149
+ @query_string = QueryString.primitive_new_for_raw_query_string(env['QUERY_STRING'] || '')
150
+ @server_protocol = env['SERVER_PROTOCOL'] || ''
151
+ @remote_addr = env['REMOTE_ADDR'] || ''
152
+ @content_type = env['CONTENT_TYPE'] || ''
153
+
154
+ # non-standard:
155
+ @request_uri = env['REQUEST_URI'] # Apache
156
+
157
+ # hobix action uri
158
+ @action_uri = ( env['PATH_INFO'] || env['REQUEST_URI'] ).
159
+ gsub( /^(#{ Regexp::quote( File::dirname( @script_name ) ) })?\/*/, '' ).
160
+ gsub( /\?.+$/, '' )
161
+ end
162
+ end
163
+
164
+ class Response < Message
165
+ def initialize(status_line='200 OK', header={}, body='')
166
+ @status_line = status_line
167
+ super header, body
168
+ end
169
+ attr_accessor :status_line
170
+
171
+ def output_cgi_status_field(out)
172
+ out << "Status: #{self.status_line}\n"
173
+ end
174
+ end
175
+ # :startdoc:
176
+ end
177
+ end