| @@ -0,0 +1,217 @@ | ||
| require "uri" | ||
| require "net/https" | ||
| require "resolv" | ||
| require "ipaddr" | ||
| class Sponge | ||
| MAX_TIME = 60 | ||
| MAX_DNS_TIME = 5 | ||
| attr_accessor :debug, :last_res, :timeout | ||
| # rfc3330 | ||
| BAD_NETS = [ | ||
| "0.0.0.0/8", | ||
| "10.0.0.0/8", | ||
| "127.0.0.0/8", | ||
| "169.254.0.0/16", | ||
| "172.16.0.0/12", | ||
| "192.0.2.0/24", | ||
| "192.88.99.0/24", | ||
| "192.168.0.0/16", | ||
| "198.18.0.0/15", | ||
| "224.0.0.0/4", | ||
| "240.0.0.0/4" | ||
| ] | ||
| # old api | ||
| def self.fetch(url, headers = {}, limit = 10) | ||
| s = Sponge.new | ||
| s.fetch(url, "get", nil, nil, headers, limit) | ||
| end | ||
| def initialize | ||
| @cookies = {} | ||
| @timeout = MAX_TIME | ||
| end | ||
| def set_cookie(host, name, val) | ||
| dputs "setting cookie #{name} on domain #{host} to #{val}" | ||
| if !@cookies[host] | ||
| @cookies[host] = {} | ||
| end | ||
| if val.to_s == "" | ||
| @cookies[host][name] ? @cookies[host][name].delete : nil | ||
| else | ||
| @cookies[host][name] = val | ||
| end | ||
| end | ||
| def cookies(host) | ||
| cooks = @cookies[host] || {} | ||
| # check for domain cookies | ||
| @cookies.keys.each do |dom| | ||
| if dom.length < host.length && | ||
| dom == host[host.length - dom.length .. host.length - 1] | ||
| dputs "adding domain keys from #{dom}" | ||
| cooks = cooks.merge @cookies[dom] | ||
| end | ||
| end | ||
| if cooks | ||
| return cooks.map{|k,v| "#{k}=#{v};" }.join(" ") | ||
| else | ||
| return "" | ||
| end | ||
| end | ||
| def fetch(url, method = :get, fields = nil, raw_post_data = nil, | ||
| headers = {}, limit = 10) | ||
| raise ArgumentError, "http redirection too deep" if limit <= 0 | ||
| uri = URI.parse(url) | ||
| # we'll manually resolve the ip so we can verify it's not local | ||
| ip = nil | ||
| tip = nil | ||
| ips = [] | ||
| retried = false | ||
| begin | ||
| Timeout.timeout(MAX_DNS_TIME) do | ||
| ips = Resolv.getaddresses(uri.host) | ||
| if !ips.any? | ||
| raise | ||
| end | ||
| # pick a random one | ||
| tip = ips[rand(ips.length)] | ||
| ip = IPAddr.new(tip) | ||
| end | ||
| rescue Timeout::Error => e | ||
| if retried | ||
| raise "couldn't resolve #{uri.host} (DNS timeout)" | ||
| else | ||
| retried = true | ||
| retry | ||
| end | ||
| rescue StandardError => e | ||
| raise "couldn't resolve #{uri.host} (#{e.inspect})" | ||
| end | ||
| if !ip | ||
| raise "couldn't resolve #{uri.host}" | ||
| end | ||
| if BAD_NETS.select{|n| IPAddr.new(n).include?(ip) }.any? | ||
| raise "refusing to talk to IP #{ip.to_s}" | ||
| end | ||
| host = Net::HTTP.new(ip.to_s, uri.port) | ||
| if self.debug | ||
| host.set_debug_output $stdout | ||
| end | ||
| if uri.scheme == "https" | ||
| host.use_ssl = true | ||
| host.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||
| end | ||
| path = (uri.path == "" ? "/" : uri.path) | ||
| if uri.query | ||
| path += "?" + uri.query | ||
| elsif method == :get && raw_post_data | ||
| path += "?" + URI.encode(raw_post_data) | ||
| headers["Content-type"] = "application/x-www-form-urlencoded" | ||
| end | ||
| if method == :post | ||
| if raw_post_data | ||
| post_data = raw_post_data | ||
| headers["Content-type"] = "application/x-www-form-urlencoded" | ||
| else | ||
| post_data = fields.map{|k,v| "#{k}=#{v}" }.join("&") | ||
| end | ||
| headers["Content-Length"] = post_data.length.to_s | ||
| end | ||
| path.gsub!(/^\/\//, "/") | ||
| dputs "fetching #{url} (#{ip.to_s}) " + (uri.user ? "with http auth " + | ||
| uri.user + "/" + ("*" * uri.password.length) + " " : "") + | ||
| "by #{method} with cookies #{cookies(uri.host)}" | ||
| headers = { | ||
| "Host" => uri.host, | ||
| "Cookie" => cookies(uri.host), | ||
| "Referer" => url.to_s, | ||
| "User-Agent" => "Mozilla/5.0 (compatible)", | ||
| }.merge(headers || {}) | ||
| if uri.user | ||
| headers["Authorization"] = "Basic " + | ||
| ["#{uri.user}:#{uri.password}"].pack('m').delete("\r\n") | ||
| end | ||
| res = nil | ||
| Timeout.timeout(self.timeout) do | ||
| if method == :post | ||
| res = host.post(path, post_data, headers) | ||
| else | ||
| res = host.get(path, headers) | ||
| end | ||
| end | ||
| if res.get_fields("Set-Cookie") | ||
| res.get_fields("Set-Cookie").each do |cook| | ||
| if p = Regexp.new(/^([^=]+)=([^;]*)/).match(cook) | ||
| set_cookie(uri.host, p[1], p[2]) | ||
| else | ||
| dputs "unable to match cookie line #{cook}" | ||
| end | ||
| end | ||
| end | ||
| last_res = res | ||
| case res | ||
| when Net::HTTPSuccess | ||
| return res.body | ||
| when Net::HTTPRedirection | ||
| # follow | ||
| newuri = URI.parse(res["location"]) | ||
| if newuri.host | ||
| dputs "following redirection to " + res["location"] | ||
| else | ||
| # relative path | ||
| newuri.host = uri.host | ||
| newuri.scheme = uri.scheme | ||
| newuri.port = uri.port | ||
| newuri.path = "/#{newuri.path}" | ||
| dputs "following relative redirection to " + newuri.to_s | ||
| end | ||
| fetch(newuri.to_s, "get", nil, nil, headers, limit - 1) | ||
| end | ||
| end | ||
| def get(url) | ||
| fetch(url, "get") | ||
| end | ||
| def post(url, fields) | ||
| fetch(url, "post", fields) | ||
| end | ||
| private | ||
| def dputs(string) | ||
| if self.debug | ||
| puts string | ||
| end | ||
| end | ||
| end |
| @@ -0,0 +1,16 @@ | ||
| class Utils | ||
| def self.random_str(len) | ||
| str = "" | ||
| while str.length < len | ||
| chr = OpenSSL::Random.random_bytes(1) | ||
| ord = chr.unpack('C')[0] | ||
| # 0 9 A Z a z | ||
| if (ord >= 48 && ord <= 57) || (ord >= 65 && ord <= 90) || (ord >= 97 && ord <= 122) | ||
| str += chr | ||
| end | ||
| end | ||
| return str | ||
| end | ||
| end |
| @@ -0,0 +1,26 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>The page you were looking for doesn't exist (404)</title> | ||
| <style type="text/css"> | ||
| body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } | ||
| div.dialog { | ||
| width: 25em; | ||
| padding: 0 4em; | ||
| margin: 4em auto 0 auto; | ||
| border: 1px solid #ccc; | ||
| border-right-color: #999; | ||
| border-bottom-color: #999; | ||
| } | ||
| h1 { font-size: 100%; color: #f00; line-height: 1.5em; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <!-- This file lives in public/404.html --> | ||
| <div class="dialog"> | ||
| <h1>The page you were looking for doesn't exist.</h1> | ||
| <p>You may have mistyped the address or the page may have moved.</p> | ||
| </div> | ||
| </body> | ||
| </html> |
| @@ -0,0 +1,26 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>The change you wanted was rejected (422)</title> | ||
| <style type="text/css"> | ||
| body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } | ||
| div.dialog { | ||
| width: 25em; | ||
| padding: 0 4em; | ||
| margin: 4em auto 0 auto; | ||
| border: 1px solid #ccc; | ||
| border-right-color: #999; | ||
| border-bottom-color: #999; | ||
| } | ||
| h1 { font-size: 100%; color: #f00; line-height: 1.5em; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <!-- This file lives in public/422.html --> | ||
| <div class="dialog"> | ||
| <h1>The change you wanted was rejected.</h1> | ||
| <p>Maybe you tried to change something you didn't have access to.</p> | ||
| </div> | ||
| </body> | ||
| </html> |
| @@ -0,0 +1,25 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>We're sorry, but something went wrong (500)</title> | ||
| <style type="text/css"> | ||
| body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } | ||
| div.dialog { | ||
| width: 25em; | ||
| padding: 0 4em; | ||
| margin: 4em auto 0 auto; | ||
| border: 1px solid #ccc; | ||
| border-right-color: #999; | ||
| border-bottom-color: #999; | ||
| } | ||
| h1 { font-size: 100%; color: #f00; line-height: 1.5em; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <!-- This file lives in public/500.html --> | ||
| <div class="dialog"> | ||
| <h1>We're sorry, but something went wrong.</h1> | ||
| </div> | ||
| </body> | ||
| </html> |
| @@ -0,0 +1,5 @@ | ||
| # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file | ||
| # | ||
| # To ban all spiders from the entire site uncomment the next two lines: | ||
| # User-Agent: * | ||
| # Disallow: / |
| @@ -0,0 +1,6 @@ | ||
| #!/usr/bin/env ruby | ||
| # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. | ||
| APP_PATH = File.expand_path('../../config/application', __FILE__) | ||
| require File.expand_path('../../config/boot', __FILE__) | ||
| require 'rails/commands' |
| @@ -0,0 +1,104 @@ | ||
| require "spec_helper" | ||
| def m(inp, out) | ||
| Markdowner::markdown(inp).should == out | ||
| end | ||
| describe Markdowner do | ||
| it "converts indented text into <pre>" do | ||
| m " This is some\n text.\n", | ||
| "<p><pre> This is some\n text.\n</pre></p>" | ||
| m " blah <script>alert('hi');</script>", | ||
| "<p><pre> blah <script>alert('hi');</script>\n</pre></p>" | ||
| end | ||
| it "converts text surrounded by * to <em>" do | ||
| m "oh hullo *there*", | ||
| "<p>oh hullo <em>there</em></p>" | ||
| m "*hi*", | ||
| "<p><em>hi</em></p>" | ||
| m "* hi hello*zap zap*", | ||
| "<p>* hi hello*zap zap*</p>" | ||
| m "oh hullo * there*", | ||
| "<p>oh hullo * there*</p>" | ||
| m " oh hullo *there*", | ||
| "<p><pre> oh hullo *there*\n</pre></p>" | ||
| m "oh hullo*there*", | ||
| "<p>oh hullo*there*</p>" | ||
| end | ||
| it "converts text surrounded by _ to <u>" do | ||
| m "oh hullo _there_", | ||
| "<p>oh hullo <u>there</u></p>" | ||
| m "oh hullo _ there_", | ||
| "<p>oh hullo _ there_</p>" | ||
| m "oh hullo _there_ and *yes* i see", | ||
| "<p>oh hullo <u>there</u> and <em>yes</em> i see</p>" | ||
| end | ||
| it "combines conversions" do | ||
| m "oh _*hullo*_ there_", | ||
| "<p>oh <u><em>hullo</em></u> there_</p>" | ||
| m "oh *_hullo_* there_", | ||
| "<p>oh <em><u>hullo</u></em> there_</p>" | ||
| m "oh *[hello](http://jcs.org/)* there_", | ||
| "<p>oh <em><a href=\"http://jcs.org/\" rel=\"nofollow\">hello</a>" << | ||
| "</em> there_</p>" | ||
| end | ||
| it "converts domain names to links" do | ||
| m "oh hullo www.google.com", | ||
| "<p>oh hullo <a href=\"http://www.google.com\" rel=\"nofollow\">" << | ||
| "www.google.com</a></p>" | ||
| end | ||
| it "converts urls to links" do | ||
| # no trailing question mark | ||
| m "do you mean http://jcs.org? or", | ||
| "<p>do you mean <a href=\"http://jcs.org\" rel=\"nofollow\">" << | ||
| "jcs.org</a>? or</p>" | ||
| m "do you mean http://jcs.org?a", | ||
| "<p>do you mean <a href=\"http://jcs.org?a\" rel=\"nofollow\">" << | ||
| "jcs.org?a</a></p>" | ||
| # no trailing dot in url | ||
| m "i like http://jcs.org.", | ||
| "<p>i like <a href=\"http://jcs.org\" rel=\"nofollow\">" << | ||
| "jcs.org</a>.</p>" | ||
| m "i like http://jcs.org/goose_blah_here", | ||
| "<p>i like <a href=\"http://jcs.org/goose_blah_here\" " << | ||
| "rel=\"nofollow\">jcs.org/goose_blah_here</a></p>" | ||
| end | ||
| it "truncates long url titles" do | ||
| m "a long http://www.example.com/goes/here/and/this/is/a/long/" << | ||
| "url/which/should.get.shortened.html?because=this+will+cause+" << | ||
| "the+page+to+wrap&such+ok", | ||
| "<p>a long <a href=\"http://www.example.com/goes/here/and/this/" << | ||
| "is/a/long/url/which/should.get.shortened.html?because=this+" << | ||
| "will+cause+the+page+to+wrap&such+ok\" rel=\"nofollow\">" << | ||
| "www.example.com/goes/here/and/this/is/a/long/url/w...</a></p>" | ||
| end | ||
| it "converts markdown url format to links" do | ||
| m "this is a *[link](http://example.com/)*", | ||
| "<p>this is a <em><a href=\"http://example.com/\" rel=\"nofollow\">" << | ||
| "link</a></em></p>" | ||
| m "this is a [link](http://example.com/)", | ||
| "<p>this is a <a href=\"http://example.com/\" rel=\"nofollow\">" << | ||
| "link</a></p>" | ||
| end | ||
| end |
| @@ -0,0 +1,32 @@ | ||
| # This file is copied to spec/ when you run 'rails generate rspec:install' | ||
| ENV["RAILS_ENV"] ||= 'test' | ||
| require File.expand_path("../../config/environment", __FILE__) | ||
| require 'rspec/rails' | ||
| require 'rspec/autorun' | ||
| # Requires supporting ruby files with custom matchers and macros, etc, | ||
| # in spec/support/ and its subdirectories. | ||
| Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} | ||
| RSpec.configure do |config| | ||
| # ## Mock Framework | ||
| # | ||
| # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: | ||
| # | ||
| # config.mock_with :mocha | ||
| # config.mock_with :flexmock | ||
| # config.mock_with :rr | ||
| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures | ||
| #config.fixture_path = "#{::Rails.root}/spec/fixtures" | ||
| # If you're not using ActiveRecord, or you'd prefer not to run each of your | ||
| # examples within a transaction, remove the following line or assign false | ||
| # instead of true. | ||
| config.use_transactional_fixtures = true | ||
| # If true, the base class of anonymous controllers will be inferred | ||
| # automatically. This will be the default behavior in future versions of | ||
| # rspec-rails. | ||
| config.infer_base_class_for_anonymous_controllers = false | ||
| end |
| @@ -0,0 +1,7 @@ | ||
| require 'machinist/active_record' | ||
| User.blueprint do | ||
| email { "user-#{sn}@example.com" } | ||
| password { "blah blah" } | ||
| password_confirmation { object.password } | ||
| end |