harbor 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. metadata +155 -0
@@ -0,0 +1,39 @@
1
+ gem "logging"
2
+ require "logging"
3
+ require Pathname(__FILE__).dirname + "logging/request_logger"
4
+
5
+ Logging.configure do
6
+
7
+ logger 'root' do
8
+ level :debug
9
+ appenders 'app'
10
+ end
11
+
12
+ logger 'request' do
13
+ additive false
14
+ level :info
15
+ appenders 'request'
16
+ end
17
+
18
+ logger 'error' do
19
+ additive false
20
+ level :error
21
+ appenders 'error'
22
+ end
23
+
24
+ appender 'app' do
25
+ type 'File'
26
+ filename 'log/app.log'
27
+ end
28
+
29
+ appender 'request' do
30
+ type 'File'
31
+ filename 'log/request.log'
32
+ end
33
+
34
+ appender 'error' do
35
+ type 'File'
36
+ filename 'log/error.log'
37
+ end
38
+
39
+ end
@@ -0,0 +1,84 @@
1
+ module Harbor
2
+ module LogAppenders
3
+
4
+ class Email < Logging::Appender
5
+
6
+ attr_accessor :duplicate_subject_delivery_threshold
7
+
8
+ def initialize(container, from, *addresses)
9
+ @container = container
10
+ @from = from
11
+ @addresses = addresses
12
+ @tracked_subjects = []
13
+
14
+ # Deliver emails with exact-match subjects only every 10 Minutes
15
+ @duplicate_subject_delivery_threshold = 60 * 10
16
+
17
+ super("exception_notifier", :level => :error)
18
+ end
19
+
20
+ def write(event)
21
+ unless @level > event.level
22
+ flush_expired_subjects
23
+
24
+ data = event.data
25
+ subject = data.split($/, 2)[0]
26
+
27
+ if tracked_subject = @tracked_subjects.detect { |tracked_subject| tracked_subject.subject == subject }
28
+ tracked_subject.occurances << Time.now
29
+
30
+ # Don't send the email if we've already sent one within the time threshold
31
+ return false if (Time.now - tracked_subject.sent_at) < @duplicate_subject_delivery_threshold
32
+ else
33
+ tracked_subject = TrackedSubject.new(subject)
34
+ tracked_subject.occurances << Time.now
35
+
36
+ @tracked_subjects << tracked_subject
37
+ end
38
+
39
+ mailer = @container.get(:mailer)
40
+ mailer.from = @from
41
+ mailer.to = @addresses
42
+
43
+ data << "\n(from: #{`hostname`.chomp}, PID: #{Process.pid})"
44
+
45
+ mailer.subject = "[ERROR] #{subject}"
46
+ mailer["x_priority"] = "1 (Highest)"
47
+ mailer["x_msmail_priority"] = "High"
48
+ mailer.text = if tracked_subject.occurances.size > 1
49
+ "Repeated #{tracked_subject.occurances.size} times since #{tracked_subject.sent_at.strftime('%Y-%m-%d %R%z')}\n\n#{data}"
50
+ else
51
+ data
52
+ end
53
+ mailer.send!
54
+
55
+ tracked_subject.occurances.clear
56
+ tracked_subject.sent_at = Time.now
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ ##
63
+ # Flushes items from the @tracked_subjects array when they are no longer useful
64
+ ##
65
+ def flush_expired_subjects
66
+ @tracked_subjects.reject! do |tracked_subject|
67
+ tracked_subject.occurances.empty? && (Time.now - tracked_subject.sent_at) >= @duplicate_subject_delivery_threshold
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ class Email::TrackedSubject
74
+ attr_accessor :sent_at
75
+ attr_reader :subject, :occurances
76
+
77
+ def initialize(subject)
78
+ @subject = subject
79
+ @occurances = []
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,34 @@
1
+ module Harbor
2
+ class RequestLogger
3
+ ##
4
+ # Logs requests and their params the configured request logger.
5
+ #
6
+ # Format:
7
+ #
8
+ # #application #time #duration #ip #method #uri #status #params
9
+ # [PhotoManagement] [04-02-2009 @ 14:22:40] [0.12s] [64.134.226.23] [GET] /products (200) {"order" => "desc"}
10
+ ##
11
+ def self.info(request, response, start_time, end_time)
12
+ case
13
+ when response.status >= 500 then status = "\033[0;31m#{response.status}\033[0m" # prints the status value in red
14
+ when response.status >= 400 then status = "\033[0;33m#{response.status}\033[0m" # prints the status value in yellow
15
+ else status = "\033[0;32m#{response.status}\033[0m" # prints the status value in green
16
+ end
17
+
18
+ message = "[#{self.class}] [#{start_time.strftime('%m-%d-%Y @ %H:%M:%S')}] [#{"%2.2fs" % (end_time - start_time)}] [#{request.remote_ip}] [#{request.request_method}] #{request.path_info} (#{status})"
19
+ message << "\t #{request.params.inspect}" unless request.params.empty?
20
+ message << "\n"
21
+
22
+ if (request_logger = Logging::Logger["request"]).info?
23
+ request_logger << message
24
+ end
25
+ end
26
+
27
+ def self.error(exception, request, response, trace)
28
+ Logging::Logger['error'] << trace
29
+ end
30
+ end
31
+ end
32
+
33
+ Harbor::Application.register_event(:request_complete) { |*args| Harbor::RequestLogger.info(*args) }
34
+ Harbor::Application.register_event(:exception) { |*args| Harbor::RequestLogger.error(*args) }
@@ -0,0 +1,12 @@
1
+ module Harbor
2
+ module MailServers
3
+ class Abstract
4
+ def initialize(config = {})
5
+ end
6
+
7
+ def deliver(message_or_messages)
8
+ raise NotImplementedError.new("Classes extending #{self.class.name} must implement #deliver(message_or_messages).")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module Harbor
2
+ module MailServers
3
+ class Sendmail < Abstract
4
+ def initialize(config = {})
5
+ @sendmail = config[:sendmail] || `which sendmail`.chomp
6
+ end
7
+
8
+ def deliver(message_or_messages)
9
+ messages = Array === message_or_messages ? message_or_messages : [message_or_messages]
10
+
11
+ messages.each do |message|
12
+ sendmail = ::IO.popen("#{@sendmail} -i -t", "w+")
13
+ sendmail.puts message.to_s
14
+ sendmail.close
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ require "net/smtp"
2
+
3
+ module Harbor
4
+ module MailServers
5
+ class SmtpServer < Abstract
6
+ def initialize(config = {})
7
+ @config = {}
8
+ raise ArgumentError("You must provide the :address to your SMTP server in the SmtpServer config.") unless config.has_key?(:address)
9
+
10
+ @config[:address] = config[:address]
11
+ @config[:port] = config.fetch(:port, 25)
12
+ end
13
+
14
+ def deliver(message_or_messages)
15
+ messages = Array === message_or_messages ? message_or_messages : [message_or_messages]
16
+
17
+ Net::SMTP.start(@config[:address], @config[:port]) do |smtp|
18
+ messages.each do |message|
19
+ smtp.send_message(message.to_s, message.from, message.to)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Harbor
2
+ module MailServers
3
+ class Test < Abstract
4
+ attr_accessor :messages
5
+
6
+ def initialize(config = {})
7
+ self.messages = []
8
+ end
9
+
10
+ def deliver(message_or_messages)
11
+ messages = Array === message_or_messages ? message_or_messages : [message_or_messages]
12
+
13
+ messages.push(*messages)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,96 @@
1
+ require "cgi"
2
+
3
+ gem "mail_builder"
4
+ require "mail_builder"
5
+
6
+ require Pathname(__FILE__).dirname + "mail_servers/abstract"
7
+ require Pathname(__FILE__).dirname + "mail_servers/sendmail"
8
+ require Pathname(__FILE__).dirname + "mail_servers/smtp"
9
+ require Pathname(__FILE__).dirname + "mail_servers/test"
10
+
11
+ module Harbor
12
+ class Mailer < MailBuilder
13
+
14
+ def self.layout=(layout)
15
+ @@layout = layout
16
+ end
17
+
18
+ def self.layout
19
+ @@layout rescue nil
20
+ end
21
+
22
+ def self.host=(host)
23
+ @@host = host
24
+ end
25
+
26
+ def self.host
27
+ @@host rescue "www.example.com"
28
+ end
29
+
30
+ attr_accessor :mail_server
31
+
32
+ def initialize(*args)
33
+ super
34
+
35
+ @layout = self.class.layout
36
+ @host = self.class.host
37
+ end
38
+
39
+ ##
40
+ # We want to automatically assign the value of @mailer to self whenever
41
+ # a view is passed to the mailer object. This lets us use, for instance,
42
+ # the envelope_id to track click-through's and bounces using the same
43
+ # identifier.
44
+ #
45
+ %w(html= text=).each do |method|
46
+ define_method(method) do |value|
47
+ if value.is_a?(Harbor::View)
48
+ value.context.merge(:mailer => self)
49
+ end
50
+
51
+ if @layout && layout = @layout.dup
52
+ layout.sub!(/(\.html\.erb$)|$/, ".txt.erb") if method == "text="
53
+ value = Harbor::View.new(layout, :content => value, :mailer => self)
54
+ end
55
+
56
+ super(value)
57
+ end
58
+ end
59
+
60
+ def host
61
+ @host
62
+ end
63
+
64
+ ##
65
+ # Tokenizes urls in the email body by replacing them with the mail_server_url
66
+ # provided. The message's envelope_id and a base64 encoded version of the original
67
+ # url are passed to the URL provided.
68
+ #
69
+ # mailer.html = 'Please visit <a href="http://example.com">our site</a> for details.'
70
+ # mailer.tokenize_urls!("http://example.com/.m/%s?redirect=%s")
71
+ # mailer.html # => "Please visit <a href=\"http://example.com/.m/%2AF%2Ch2Gtn.ny1poJnnvvCeSMZA?redirect=aHR0cDovL2V4YW1wbGUuY29t%0A\">our site</a> for details."
72
+ ##
73
+ def tokenize_urls!(mail_server_url)
74
+ mail_server_url = "http://#{mail_server_url}" unless mail_server_url =~ /^http/
75
+
76
+ [:@html, :@text].each do |ivar|
77
+ if content = instance_variable_get(ivar)
78
+ new_content = content.to_s.gsub(/(https?:\/\/.+?(?=[" <]|$))(\W*)(.{4}|$)/) do |url|
79
+ # Don't tokenize the inner text of a link
80
+ if $3 == '</a>'
81
+ url
82
+ else
83
+ (mail_server_url % [CGI.escape(envelope_id), CGI.escape([$1].pack("m"))]) + $2 + $3
84
+ end
85
+ end
86
+ instance_variable_set(ivar, new_content)
87
+ end
88
+ end
89
+ end
90
+
91
+ def send!
92
+ mail_server.deliver(self)
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,17 @@
1
+ module Harbor
2
+ class Messages < Hash
3
+ def initialize(messages = {})
4
+ super()
5
+ merge!(messages) if messages
6
+ end
7
+
8
+ def [](key)
9
+ @expired = true
10
+ super
11
+ end
12
+
13
+ def expired?
14
+ !!@expired
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,206 @@
1
+ module Harbor
2
+ # Copyright (c) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
3
+ # This file originated from the Rack project, which can be found at rack.rubyforge.org
4
+ module Mime
5
+ # Returns String with mime type if found, otherwise use +fallback+.
6
+ # +ext+ should be filename extension in the '.ext' format that
7
+ # File.extname(file) returns.
8
+ # +fallback+ may be any object
9
+ #
10
+ # Also see the documentation for MIME_TYPES
11
+ #
12
+ # Usage:
13
+ # Harbor::Mime.mime_type('.foo')
14
+ #
15
+ # This is a shortcut for:
16
+ # Harbor::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
17
+
18
+ def mime_type(ext, fallback='application/octet-stream')
19
+ MIME_TYPES.fetch(ext, fallback)
20
+ end
21
+ module_function :mime_type
22
+
23
+ # List of most common mime-types, selected various sources
24
+ # according to their usefulness in a webserving scope for Ruby
25
+ # users.
26
+ #
27
+ # To amend this list with your local mime.types list you can use:
28
+ #
29
+ # require 'webrick/httputils'
30
+ # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
31
+ # Harbor::Mime::MIME_TYPES.merge!(list)
32
+ #
33
+ # To add the list mongrel provides, use:
34
+ #
35
+ # require 'mongrel/handlers'
36
+ # Harbor::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
37
+
38
+ MIME_TYPES = {
39
+ ".3gp" => "video/3gpp",
40
+ ".a" => "application/octet-stream",
41
+ ".ai" => "application/postscript",
42
+ ".aif" => "audio/x-aiff",
43
+ ".aiff" => "audio/x-aiff",
44
+ ".asc" => "application/pgp-signature",
45
+ ".asf" => "video/x-ms-asf",
46
+ ".asm" => "text/x-asm",
47
+ ".asx" => "video/x-ms-asf",
48
+ ".atom" => "application/atom+xml",
49
+ ".au" => "audio/basic",
50
+ ".avi" => "video/x-msvideo",
51
+ ".bat" => "application/x-msdownload",
52
+ ".bin" => "application/octet-stream",
53
+ ".bmp" => "image/bmp",
54
+ ".bz2" => "application/x-bzip2",
55
+ ".c" => "text/x-c",
56
+ ".cab" => "application/vnd.ms-cab-compressed",
57
+ ".cc" => "text/x-c",
58
+ ".chm" => "application/vnd.ms-htmlhelp",
59
+ ".class" => "application/octet-stream",
60
+ ".com" => "application/x-msdownload",
61
+ ".conf" => "text/plain",
62
+ ".cpp" => "text/x-c",
63
+ ".crt" => "application/x-x509-ca-cert",
64
+ ".css" => "text/css",
65
+ ".csv" => "text/csv",
66
+ ".cxx" => "text/x-c",
67
+ ".deb" => "application/x-debian-package",
68
+ ".der" => "application/x-x509-ca-cert",
69
+ ".diff" => "text/x-diff",
70
+ ".djv" => "image/vnd.djvu",
71
+ ".djvu" => "image/vnd.djvu",
72
+ ".dll" => "application/x-msdownload",
73
+ ".dmg" => "application/octet-stream",
74
+ ".doc" => "application/msword",
75
+ ".dot" => "application/msword",
76
+ ".dtd" => "application/xml-dtd",
77
+ ".dvi" => "application/x-dvi",
78
+ ".ear" => "application/java-archive",
79
+ ".eml" => "message/rfc822",
80
+ ".eps" => "application/postscript",
81
+ ".exe" => "application/x-msdownload",
82
+ ".f" => "text/x-fortran",
83
+ ".f77" => "text/x-fortran",
84
+ ".f90" => "text/x-fortran",
85
+ ".flv" => "video/x-flv",
86
+ ".for" => "text/x-fortran",
87
+ ".gem" => "application/octet-stream",
88
+ ".gemspec" => "text/x-script.ruby",
89
+ ".gif" => "image/gif",
90
+ ".gz" => "application/x-gzip",
91
+ ".h" => "text/x-c",
92
+ ".hh" => "text/x-c",
93
+ ".htm" => "text/html",
94
+ ".html" => "text/html",
95
+ ".ico" => "image/vnd.microsoft.icon",
96
+ ".ics" => "text/calendar",
97
+ ".ifb" => "text/calendar",
98
+ ".iso" => "application/octet-stream",
99
+ ".jar" => "application/java-archive",
100
+ ".java" => "text/x-java-source",
101
+ ".jnlp" => "application/x-java-jnlp-file",
102
+ ".jpeg" => "image/jpeg",
103
+ ".jpg" => "image/jpeg",
104
+ ".js" => "application/javascript",
105
+ ".json" => "application/json",
106
+ ".log" => "text/plain",
107
+ ".m3u" => "audio/x-mpegurl",
108
+ ".m4v" => "video/mp4",
109
+ ".man" => "text/troff",
110
+ ".mathml" => "application/mathml+xml",
111
+ ".mbox" => "application/mbox",
112
+ ".mdoc" => "text/troff",
113
+ ".me" => "text/troff",
114
+ ".mid" => "audio/midi",
115
+ ".midi" => "audio/midi",
116
+ ".mime" => "message/rfc822",
117
+ ".mml" => "application/mathml+xml",
118
+ ".mng" => "video/x-mng",
119
+ ".mov" => "video/quicktime",
120
+ ".mp3" => "audio/mpeg",
121
+ ".mp4" => "video/mp4",
122
+ ".mp4v" => "video/mp4",
123
+ ".mpeg" => "video/mpeg",
124
+ ".mpg" => "video/mpeg",
125
+ ".ms" => "text/troff",
126
+ ".msi" => "application/x-msdownload",
127
+ ".odp" => "application/vnd.oasis.opendocument.presentation",
128
+ ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
129
+ ".odt" => "application/vnd.oasis.opendocument.text",
130
+ ".ogg" => "application/ogg",
131
+ ".p" => "text/x-pascal",
132
+ ".pas" => "text/x-pascal",
133
+ ".pbm" => "image/x-portable-bitmap",
134
+ ".pdf" => "application/pdf",
135
+ ".pem" => "application/x-x509-ca-cert",
136
+ ".pgm" => "image/x-portable-graymap",
137
+ ".pgp" => "application/pgp-encrypted",
138
+ ".pkg" => "application/octet-stream",
139
+ ".pl" => "text/x-script.perl",
140
+ ".pm" => "text/x-script.perl-module",
141
+ ".png" => "image/png",
142
+ ".pnm" => "image/x-portable-anymap",
143
+ ".ppm" => "image/x-portable-pixmap",
144
+ ".pps" => "application/vnd.ms-powerpoint",
145
+ ".ppt" => "application/vnd.ms-powerpoint",
146
+ ".ps" => "application/postscript",
147
+ ".psd" => "image/vnd.adobe.photoshop",
148
+ ".py" => "text/x-script.python",
149
+ ".qt" => "video/quicktime",
150
+ ".ra" => "audio/x-pn-realaudio",
151
+ ".rake" => "text/x-script.ruby",
152
+ ".ram" => "audio/x-pn-realaudio",
153
+ ".rar" => "application/x-rar-compressed",
154
+ ".rb" => "text/x-script.ruby",
155
+ ".rdf" => "application/rdf+xml",
156
+ ".roff" => "text/troff",
157
+ ".rpm" => "application/x-redhat-package-manager",
158
+ ".rss" => "application/rss+xml",
159
+ ".rtf" => "application/rtf",
160
+ ".ru" => "text/x-script.ruby",
161
+ ".s" => "text/x-asm",
162
+ ".sgm" => "text/sgml",
163
+ ".sgml" => "text/sgml",
164
+ ".sh" => "application/x-sh",
165
+ ".sig" => "application/pgp-signature",
166
+ ".snd" => "audio/basic",
167
+ ".so" => "application/octet-stream",
168
+ ".svg" => "image/svg+xml",
169
+ ".svgz" => "image/svg+xml",
170
+ ".swf" => "application/x-shockwave-flash",
171
+ ".t" => "text/troff",
172
+ ".tar" => "application/x-tar",
173
+ ".tbz" => "application/x-bzip-compressed-tar",
174
+ ".tcl" => "application/x-tcl",
175
+ ".tex" => "application/x-tex",
176
+ ".texi" => "application/x-texinfo",
177
+ ".texinfo" => "application/x-texinfo",
178
+ ".text" => "text/plain",
179
+ ".tif" => "image/tiff",
180
+ ".tiff" => "image/tiff",
181
+ ".torrent" => "application/x-bittorrent",
182
+ ".tr" => "text/troff",
183
+ ".txt" => "text/plain",
184
+ ".vcf" => "text/x-vcard",
185
+ ".vcs" => "text/x-vcalendar",
186
+ ".vrml" => "model/vrml",
187
+ ".war" => "application/java-archive",
188
+ ".wav" => "audio/x-wav",
189
+ ".wma" => "audio/x-ms-wma",
190
+ ".wmv" => "video/x-ms-wmv",
191
+ ".wmx" => "video/x-ms-wmx",
192
+ ".wrl" => "model/vrml",
193
+ ".wsdl" => "application/wsdl+xml",
194
+ ".xbm" => "image/x-xbitmap",
195
+ ".xhtml" => "application/xhtml+xml",
196
+ ".xls" => "application/vnd.ms-excel",
197
+ ".xml" => "application/xml",
198
+ ".xpm" => "image/x-xpixmap",
199
+ ".xsl" => "application/xml",
200
+ ".xslt" => "application/xslt+xml",
201
+ ".yaml" => "text/yaml",
202
+ ".yml" => "text/yaml",
203
+ ".zip" => "application/zip",
204
+ }
205
+ end
206
+ end