spec_lint.rb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. require 'stringio'
  2. require 'rack/lint'
  3. require 'rack/mock'
  4. describe Rack::Lint do
  5. def env(*args)
  6. Rack::MockRequest.env_for("/", *args)
  7. end
  8. should "pass valid request" do
  9. lambda {
  10. Rack::Lint.new(lambda { |env|
  11. [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
  12. }).call(env({}))
  13. }.should.not.raise
  14. end
  15. should "notice fatal errors" do
  16. lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
  17. message.should.match(/No env given/)
  18. end
  19. should "notice environment errors" do
  20. lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
  21. message.should.match(/not a Hash/)
  22. lambda {
  23. e = env
  24. e.delete("REQUEST_METHOD")
  25. Rack::Lint.new(nil).call(e)
  26. }.should.raise(Rack::Lint::LintError).
  27. message.should.match(/missing required key REQUEST_METHOD/)
  28. lambda {
  29. e = env
  30. e.delete("SERVER_NAME")
  31. Rack::Lint.new(nil).call(e)
  32. }.should.raise(Rack::Lint::LintError).
  33. message.should.match(/missing required key SERVER_NAME/)
  34. lambda {
  35. Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
  36. }.should.raise(Rack::Lint::LintError).
  37. message.should.match(/contains HTTP_CONTENT_TYPE/)
  38. lambda {
  39. Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
  40. }.should.raise(Rack::Lint::LintError).
  41. message.should.match(/contains HTTP_CONTENT_LENGTH/)
  42. lambda {
  43. Rack::Lint.new(nil).call(env("FOO" => Object.new))
  44. }.should.raise(Rack::Lint::LintError).
  45. message.should.match(/non-string value/)
  46. lambda {
  47. Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
  48. }.should.raise(Rack::Lint::LintError).
  49. message.should.match(/must be an Array/)
  50. lambda {
  51. Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
  52. }.should.raise(Rack::Lint::LintError).
  53. message.should.match(/url_scheme unknown/)
  54. lambda {
  55. Rack::Lint.new(nil).call(env("rack.session" => []))
  56. }.should.raise(Rack::Lint::LintError).
  57. message.should.equal("session [] must respond to store and []=")
  58. lambda {
  59. Rack::Lint.new(nil).call(env("rack.logger" => []))
  60. }.should.raise(Rack::Lint::LintError).
  61. message.should.equal("logger [] must respond to info")
  62. lambda {
  63. Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
  64. }.should.raise(Rack::Lint::LintError).
  65. message.should.match(/REQUEST_METHOD/)
  66. lambda {
  67. Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
  68. }.should.raise(Rack::Lint::LintError).
  69. message.should.match(/must start with/)
  70. lambda {
  71. Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
  72. }.should.raise(Rack::Lint::LintError).
  73. message.should.match(/must start with/)
  74. lambda {
  75. Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
  76. }.should.raise(Rack::Lint::LintError).
  77. message.should.match(/Invalid CONTENT_LENGTH/)
  78. lambda {
  79. e = env
  80. e.delete("PATH_INFO")
  81. e.delete("SCRIPT_NAME")
  82. Rack::Lint.new(nil).call(e)
  83. }.should.raise(Rack::Lint::LintError).
  84. message.should.match(/One of .* must be set/)
  85. lambda {
  86. Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
  87. }.should.raise(Rack::Lint::LintError).
  88. message.should.match(/cannot be .* make it ''/)
  89. end
  90. should "notice input errors" do
  91. lambda {
  92. Rack::Lint.new(nil).call(env("rack.input" => ""))
  93. }.should.raise(Rack::Lint::LintError).
  94. message.should.match(/does not respond to #gets/)
  95. lambda {
  96. input = Object.new
  97. def input.binmode?
  98. false
  99. end
  100. Rack::Lint.new(nil).call(env("rack.input" => input))
  101. }.should.raise(Rack::Lint::LintError).
  102. message.should.match(/is not opened in binary mode/)
  103. lambda {
  104. input = Object.new
  105. def input.external_encoding
  106. result = Object.new
  107. def result.name
  108. "US-ASCII"
  109. end
  110. result
  111. end
  112. Rack::Lint.new(nil).call(env("rack.input" => input))
  113. }.should.raise(Rack::Lint::LintError).
  114. message.should.match(/does not have ASCII-8BIT as its external encoding/)
  115. end
  116. should "notice error errors" do
  117. lambda {
  118. Rack::Lint.new(nil).call(env("rack.errors" => ""))
  119. }.should.raise(Rack::Lint::LintError).
  120. message.should.match(/does not respond to #puts/)
  121. end
  122. should "notice status errors" do
  123. lambda {
  124. Rack::Lint.new(lambda { |env|
  125. ["cc", {}, ""]
  126. }).call(env({}))
  127. }.should.raise(Rack::Lint::LintError).
  128. message.should.match(/must be >=100 seen as integer/)
  129. lambda {
  130. Rack::Lint.new(lambda { |env|
  131. [42, {}, ""]
  132. }).call(env({}))
  133. }.should.raise(Rack::Lint::LintError).
  134. message.should.match(/must be >=100 seen as integer/)
  135. end
  136. should "notice header errors" do
  137. lambda {
  138. Rack::Lint.new(lambda { |env|
  139. [200, Object.new, []]
  140. }).call(env({}))
  141. }.should.raise(Rack::Lint::LintError).
  142. message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
  143. lambda {
  144. Rack::Lint.new(lambda { |env|
  145. [200, {true=>false}, []]
  146. }).call(env({}))
  147. }.should.raise(Rack::Lint::LintError).
  148. message.should.equal("header key must be a string, was TrueClass")
  149. lambda {
  150. Rack::Lint.new(lambda { |env|
  151. [200, {"Status" => "404"}, []]
  152. }).call(env({}))
  153. }.should.raise(Rack::Lint::LintError).
  154. message.should.match(/must not contain Status/)
  155. lambda {
  156. Rack::Lint.new(lambda { |env|
  157. [200, {"Content-Type:" => "text/plain"}, []]
  158. }).call(env({}))
  159. }.should.raise(Rack::Lint::LintError).
  160. message.should.match(/must not contain :/)
  161. lambda {
  162. Rack::Lint.new(lambda { |env|
  163. [200, {"Content-" => "text/plain"}, []]
  164. }).call(env({}))
  165. }.should.raise(Rack::Lint::LintError).
  166. message.should.match(/must not end/)
  167. lambda {
  168. Rack::Lint.new(lambda { |env|
  169. [200, {"..%%quark%%.." => "text/plain"}, []]
  170. }).call(env({}))
  171. }.should.raise(Rack::Lint::LintError).
  172. message.should.equal("invalid header name: ..%%quark%%..")
  173. lambda {
  174. Rack::Lint.new(lambda { |env|
  175. [200, {"Foo" => Object.new}, []]
  176. }).call(env({}))
  177. }.should.raise(Rack::Lint::LintError).
  178. message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
  179. lambda {
  180. Rack::Lint.new(lambda { |env|
  181. [200, {"Foo" => [1, 2, 3]}, []]
  182. }).call(env({}))
  183. }.should.raise(Rack::Lint::LintError).
  184. message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
  185. lambda {
  186. Rack::Lint.new(lambda { |env|
  187. [200, {"Foo-Bar" => "text\000plain"}, []]
  188. }).call(env({}))
  189. }.should.raise(Rack::Lint::LintError).
  190. message.should.match(/invalid header/)
  191. # line ends (010) should be allowed in header values.
  192. lambda {
  193. Rack::Lint.new(lambda { |env|
  194. [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
  195. }).call(env({}))
  196. }.should.not.raise(Rack::Lint::LintError)
  197. end
  198. should "notice content-type errors" do
  199. lambda {
  200. Rack::Lint.new(lambda { |env|
  201. [200, {"Content-length" => "0"}, []]
  202. }).call(env({}))
  203. }.should.raise(Rack::Lint::LintError).
  204. message.should.match(/No Content-Type/)
  205. [100, 101, 204, 205, 304].each do |status|
  206. lambda {
  207. Rack::Lint.new(lambda { |env|
  208. [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  209. }).call(env({}))
  210. }.should.raise(Rack::Lint::LintError).
  211. message.should.match(/Content-Type header found/)
  212. end
  213. end
  214. should "notice content-length errors" do
  215. [100, 101, 204, 205, 304].each do |status|
  216. lambda {
  217. Rack::Lint.new(lambda { |env|
  218. [status, {"Content-length" => "0"}, []]
  219. }).call(env({}))
  220. }.should.raise(Rack::Lint::LintError).
  221. message.should.match(/Content-Length header found/)
  222. end
  223. lambda {
  224. Rack::Lint.new(lambda { |env|
  225. [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
  226. }).call(env({}))[2].each { }
  227. }.should.raise(Rack::Lint::LintError).
  228. message.should.match(/Content-Length header was 1, but should be 0/)
  229. end
  230. should "notice body errors" do
  231. lambda {
  232. body = Rack::Lint.new(lambda { |env|
  233. [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
  234. }).call(env({}))[2]
  235. body.each { |part| }
  236. }.should.raise(Rack::Lint::LintError).
  237. message.should.match(/yielded non-string/)
  238. end
  239. should "notice input handling errors" do
  240. lambda {
  241. Rack::Lint.new(lambda { |env|
  242. env["rack.input"].gets("\r\n")
  243. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  244. }).call(env({}))
  245. }.should.raise(Rack::Lint::LintError).
  246. message.should.match(/gets called with arguments/)
  247. lambda {
  248. Rack::Lint.new(lambda { |env|
  249. env["rack.input"].read(1, 2, 3)
  250. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  251. }).call(env({}))
  252. }.should.raise(Rack::Lint::LintError).
  253. message.should.match(/read called with too many arguments/)
  254. lambda {
  255. Rack::Lint.new(lambda { |env|
  256. env["rack.input"].read("foo")
  257. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  258. }).call(env({}))
  259. }.should.raise(Rack::Lint::LintError).
  260. message.should.match(/read called with non-integer and non-nil length/)
  261. lambda {
  262. Rack::Lint.new(lambda { |env|
  263. env["rack.input"].read(-1)
  264. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  265. }).call(env({}))
  266. }.should.raise(Rack::Lint::LintError).
  267. message.should.match(/read called with a negative length/)
  268. lambda {
  269. Rack::Lint.new(lambda { |env|
  270. env["rack.input"].read(nil, nil)
  271. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  272. }).call(env({}))
  273. }.should.raise(Rack::Lint::LintError).
  274. message.should.match(/read called with non-String buffer/)
  275. lambda {
  276. Rack::Lint.new(lambda { |env|
  277. env["rack.input"].read(nil, 1)
  278. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  279. }).call(env({}))
  280. }.should.raise(Rack::Lint::LintError).
  281. message.should.match(/read called with non-String buffer/)
  282. lambda {
  283. Rack::Lint.new(lambda { |env|
  284. env["rack.input"].rewind(0)
  285. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  286. }).call(env({}))
  287. }.should.raise(Rack::Lint::LintError).
  288. message.should.match(/rewind called with arguments/)
  289. weirdio = Object.new
  290. class << weirdio
  291. def gets
  292. 42
  293. end
  294. def read
  295. 23
  296. end
  297. def each
  298. yield 23
  299. yield 42
  300. end
  301. def rewind
  302. raise Errno::ESPIPE, "Errno::ESPIPE"
  303. end
  304. end
  305. eof_weirdio = Object.new
  306. class << eof_weirdio
  307. def gets
  308. nil
  309. end
  310. def read(*args)
  311. nil
  312. end
  313. def each
  314. end
  315. def rewind
  316. end
  317. end
  318. lambda {
  319. Rack::Lint.new(lambda { |env|
  320. env["rack.input"].gets
  321. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  322. }).call(env("rack.input" => weirdio))
  323. }.should.raise(Rack::Lint::LintError).
  324. message.should.match(/gets didn't return a String/)
  325. lambda {
  326. Rack::Lint.new(lambda { |env|
  327. env["rack.input"].each { |x| }
  328. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  329. }).call(env("rack.input" => weirdio))
  330. }.should.raise(Rack::Lint::LintError).
  331. message.should.match(/each didn't yield a String/)
  332. lambda {
  333. Rack::Lint.new(lambda { |env|
  334. env["rack.input"].read
  335. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  336. }).call(env("rack.input" => weirdio))
  337. }.should.raise(Rack::Lint::LintError).
  338. message.should.match(/read didn't return nil or a String/)
  339. lambda {
  340. Rack::Lint.new(lambda { |env|
  341. env["rack.input"].read
  342. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  343. }).call(env("rack.input" => eof_weirdio))
  344. }.should.raise(Rack::Lint::LintError).
  345. message.should.match(/read\(nil\) returned nil on EOF/)
  346. lambda {
  347. Rack::Lint.new(lambda { |env|
  348. env["rack.input"].rewind
  349. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  350. }).call(env("rack.input" => weirdio))
  351. }.should.raise(Rack::Lint::LintError).
  352. message.should.match(/rewind raised Errno::ESPIPE/)
  353. lambda {
  354. Rack::Lint.new(lambda { |env|
  355. env["rack.input"].close
  356. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  357. }).call(env({}))
  358. }.should.raise(Rack::Lint::LintError).
  359. message.should.match(/close must not be called/)
  360. end
  361. should "notice error handling errors" do
  362. lambda {
  363. Rack::Lint.new(lambda { |env|
  364. env["rack.errors"].write(42)
  365. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  366. }).call(env({}))
  367. }.should.raise(Rack::Lint::LintError).
  368. message.should.match(/write not called with a String/)
  369. lambda {
  370. Rack::Lint.new(lambda { |env|
  371. env["rack.errors"].close
  372. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  373. }).call(env({}))
  374. }.should.raise(Rack::Lint::LintError).
  375. message.should.match(/close must not be called/)
  376. end
  377. should "notice HEAD errors" do
  378. lambda {
  379. Rack::Lint.new(lambda { |env|
  380. [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
  381. }).call(env({"REQUEST_METHOD" => "HEAD"}))
  382. }.should.not.raise
  383. lambda {
  384. Rack::Lint.new(lambda { |env|
  385. [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
  386. }).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
  387. }.should.raise(Rack::Lint::LintError).
  388. message.should.match(/body was given for HEAD/)
  389. end
  390. should "pass valid read calls" do
  391. hello_str = "hello world"
  392. hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
  393. lambda {
  394. Rack::Lint.new(lambda { |env|
  395. env["rack.input"].read
  396. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  397. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  398. }.should.not.raise(Rack::Lint::LintError)
  399. lambda {
  400. Rack::Lint.new(lambda { |env|
  401. env["rack.input"].read(0)
  402. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  403. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  404. }.should.not.raise(Rack::Lint::LintError)
  405. lambda {
  406. Rack::Lint.new(lambda { |env|
  407. env["rack.input"].read(1)
  408. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  409. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  410. }.should.not.raise(Rack::Lint::LintError)
  411. lambda {
  412. Rack::Lint.new(lambda { |env|
  413. env["rack.input"].read(nil)
  414. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  415. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  416. }.should.not.raise(Rack::Lint::LintError)
  417. lambda {
  418. Rack::Lint.new(lambda { |env|
  419. env["rack.input"].read(nil, '')
  420. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  421. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  422. }.should.not.raise(Rack::Lint::LintError)
  423. lambda {
  424. Rack::Lint.new(lambda { |env|
  425. env["rack.input"].read(1, '')
  426. [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
  427. }).call(env({"rack.input" => StringIO.new(hello_str)}))
  428. }.should.not.raise(Rack::Lint::LintError)
  429. end
  430. end
  431. describe "Rack::Lint::InputWrapper" do
  432. should "delegate :rewind to underlying IO object" do
  433. io = StringIO.new("123")
  434. wrapper = Rack::Lint::InputWrapper.new(io)
  435. wrapper.read.should.equal "123"
  436. wrapper.read.should.equal ""
  437. wrapper.rewind
  438. wrapper.read.should.equal "123"
  439. end
  440. end