PageRenderTime 41ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/fe/fcgi/handler.rb

https://github.com/nishio/ags
Ruby | 528 lines | 461 code | 63 blank | 4 comment | 62 complexity | cd7c81fddd955fd529a8af57784fbe65 MD5 | raw file
  1. require 'pstore'
  2. require 'stringio'
  3. require 'socket'
  4. class Stop < RuntimeError
  5. def initialize(s)
  6. super(s)
  7. end
  8. end
  9. class PStore
  10. def get(*k)
  11. if k.size == 1
  12. transaction(true) do
  13. self[k[0]]
  14. end
  15. else
  16. transaction(true) do
  17. k.map{|v|self[v]}
  18. end
  19. end
  20. end
  21. end
  22. class Hash
  23. def method_missing(s, *a)
  24. if key?(s)
  25. val_(self[s])
  26. elsif key?(t=s.to_s)
  27. val_(self[t])
  28. else
  29. super(s, *a)
  30. end
  31. end
  32. def val_(r)
  33. if r.class == Array && r.size == 1
  34. r[0]
  35. else
  36. r
  37. end
  38. end
  39. end
  40. class Handler
  41. @@serv = '192.168.36.2'
  42. @@port = 9999
  43. @@maintenance = File.exist?('maintenance')
  44. @@eol = "\r\n"
  45. def root_url
  46. if develop?
  47. 'http://127.0.0.1:81'
  48. else
  49. 'http://golf.shinh.org'
  50. end
  51. end
  52. def handle(req)
  53. @req = req
  54. @i = req.in
  55. @o = req.out
  56. @e = req.env
  57. @headered = nil
  58. if @e['SERVER_NAME'] == 'www.shinh.org'
  59. #if false
  60. print "Status Code: 301 Moved Permanently\r\n"
  61. l = @e['REQUEST_URI']
  62. print "Location: #{root_url}#{l}\r\n"
  63. print "\r\n"
  64. @req.finish
  65. return
  66. end
  67. begin
  68. handle_
  69. rescue
  70. if @orig_o
  71. puts end_buffering
  72. end
  73. if $!.class == Stop
  74. html_header if !@headered
  75. puts "Error:"
  76. puts $!
  77. else
  78. html_header if !@headered
  79. print "<p>#{$!}:</p><pre>#{$!.backtrace*"\n"}</pre>"
  80. end
  81. end
  82. @req.finish
  83. end
  84. def html_header(h={})
  85. @headered = true
  86. print "Content-Type: text/html; charset=UTF-8\r\n"
  87. h.each do |k, v|
  88. print "#{k}: #{v}\r\n"
  89. end
  90. print "\r\n"
  91. end
  92. def text_header(h={})
  93. @headered = true
  94. print "Content-Type: text/plain\r\n"
  95. h.each do |k, v|
  96. print "#{k}: #{v}\r\n"
  97. end
  98. print "\r\n"
  99. end
  100. def redirect(l)
  101. print "Status Code: 300 Found\r\n"
  102. print "Location: #{root_url}#{l}\r\n"
  103. print "\r\n"
  104. end
  105. def user_name
  106. @e['HTTP_COOKIE'] =~ /un=(.*)/
  107. $1 ? CGI.unescape($1) : ''
  108. end
  109. def err(e)
  110. raise Stop.new(e)
  111. end
  112. def store(d, q)
  113. db = PStore.new('db/' + d + '.db')
  114. db.transaction do
  115. q.each do |k, x|
  116. x = x[0] if x.class == Array && x.size == 1
  117. db[k] = x
  118. end
  119. end
  120. end
  121. def file_types
  122. [
  123. 'rb','pl','py','php','scm','l','arc','clj',
  124. 'ly','io','js','lua','tcl','xtal','kt','cy',
  125. 'st', 'pro','for','bas',
  126. 'pl6', 'erl', 'ijs', 'a+', 'mind',
  127. 'c','cpp','d','go',
  128. 'ml',
  129. 'hs','adb','m','java','pas','f95','cs','n','cob','curry','lmn','max','oct','reb','asy',
  130. 'awk','sed','sh','zsh','fish','wake','bc','dc',
  131. 'xgawk','m4','ps','r','vhdl','qcl',
  132. 'bf','ws','bef', 'pef', 'ms', 'gs', 'flog', 'nand',
  133. 'unl', 'lazy', 'grass', 'lamb', 'wr', 'di',
  134. 's','out','z8b','com','class','vi','grb',
  135. 'groovy', 'scala', 'logo',
  136. ]
  137. end
  138. def file_langs
  139. [
  140. 'Ruby','Perl','Python','PHP','Scheme',
  141. 'Common LISP','Arc','Clojure',
  142. 'LilyPond','Io','JavaScript','Lua','Tcl','Xtal','Kite','Cyan',
  143. 'Smalltalk', 'Prolog','Forth','BASIC',
  144. 'Perl6', 'Erlang', 'J', 'A+', 'Mind',
  145. 'C','C++','D','Go',
  146. 'OCaml',
  147. 'Haskell',
  148. 'Ada','ObjC','Java','Pascal','Fortran','C#','Nemerle','COBOL','Curry','LMNtal','Maxima','Octave','REBOL','Asymptote',
  149. 'AWK','sed','Bash','Zsh','fish','wake','bc','dc',
  150. 'xgawk','m4','Postscript','R','VHDL','QCL',
  151. 'Brainfuck','Whitespace','Befunge','Pefunge','Minus','GolfScript', 'FlogScript', 'FerNANDo',
  152. 'Unlambda','Lazy-K','Grass','Universal Lambda','Whirl','D-compile-time',
  153. 'gas','x86','z80','DOS','JVM','vi','goruby',
  154. 'Groovy', 'Scala', 'Logo',
  155. ]
  156. end
  157. def ext2lang(e)
  158. file_langs[file_types.index(e)]
  159. end
  160. def get_db(d)
  161. PStore.new('db/' + d + '.db')
  162. end
  163. def develop?
  164. @e['REMOTE_ADDR'] == '127.0.0.1'
  165. end
  166. def escape_binary(s)
  167. s.gsub(/[\x00-\x09\x0b-\x1f\x7f-\xff]/){'<span class="gray">\x%02x</span>'%$&[0]}
  168. #s
  169. end
  170. def print(*a)
  171. @o.print(*a)
  172. end
  173. def puts(*a)
  174. @o.puts(*a)
  175. end
  176. def p(*a)
  177. a.each do |x|
  178. @o.puts(x.inspect)
  179. end
  180. end
  181. def tr(*a)
  182. r = '<tr>'
  183. a.each do |x|
  184. r += "<td>#{x}</td>"
  185. end
  186. r += '</tr>'
  187. end
  188. def start_buffering
  189. @orig_o = @o
  190. @o = StringIO.new
  191. end
  192. def end_buffering
  193. @o.rewind
  194. r = @o.read
  195. @o = @orig_o
  196. @orig_o = nil
  197. r
  198. end
  199. def query
  200. CGI::parse(@i.read)
  201. end
  202. def problem_db
  203. PStore.new('db/problem.db')
  204. end
  205. def a(h, t, n=nil)
  206. if n
  207. %Q(<a href="#{h}" name="#{n}">#{t}</a>)
  208. else
  209. %Q(<a href="#{h}">#{t}</a>)
  210. end
  211. end
  212. def tag(t, c)
  213. %Q(<#{t}>#{c}</#{t}>)
  214. end
  215. def problem_url(x)
  216. '/p.rb?' + x.tr(' ','+')
  217. end
  218. def problem_summary(x, d, now)
  219. anchor = a(problem_url(x), x)
  220. if !d || d == 0
  221. anchor + " (endless)"
  222. elsif d > now
  223. anchor + " #{time_diff(d-now)} left (#{Time.at(d).strftime('%m/%d %H:%M:%S JST')})"
  224. else
  225. anchor + " (post mortem)"
  226. end
  227. end
  228. def lang_url(x)
  229. '/l.rb?' + x
  230. end
  231. def page
  232. q = @e['QUERY_STRING']
  233. err('no page') if !q
  234. [q.tr('+',' '), q]
  235. end
  236. def time_diff(dl)
  237. dls = "%02d" % (dl % 60)
  238. dlm = "%02d" % ((dl / 60) % 60)
  239. dlh = (dl / 60 / 60) % 24
  240. dld = (dl / 60 / 60 / 24)
  241. dld = dld > 0 ? "#{dld}day(s) and " : ""
  242. "#{dld}#{dlh}:#{dlm}:#{dls}"
  243. end
  244. def title(t)
  245. print %Q(<!DOCTYPE html>
  246. <html>
  247. <head>
  248. <meta http-equiv="CONTENT-TYPE" content="text/html; charset=UTF-8">
  249. <title>#{t}</title>
  250. <link rev="MADE" href="mailto:shinichiro.hamaji _at_ gmail.com">
  251. <link rel="INDEX" href=".">
  252. <link rel="stylesheet" type="text/css" href="/site.css">
  253. </head>
  254. <body>)
  255. end
  256. def foot
  257. print %Q(<p><a href="/">return top</a></p></body></html>)
  258. end
  259. def mircbot(msg)
  260. begin
  261. sock = TCPSocket.new('localhost',9999)
  262. sock.print(msg)
  263. sock.close
  264. rescue
  265. end
  266. end
  267. def lranking(ext, l, pn, del=nil, pm=0)
  268. ret = %Q(<table border="1"><tr><th>Rank</th><th>User</th><th>Size</th><th>Time</th><th>Date</th><th><a href="bas.html">Statistics</a></th>)
  269. if del
  270. ret += del[0]
  271. end
  272. ret += %Q(</tr>)
  273. l.each_with_index do |v, i|
  274. st = v[4]
  275. if st
  276. if st[1]
  277. st = [st[0], st[2], st[3]]
  278. else
  279. st = [st[0], '?', '?']
  280. end
  281. else
  282. st = ['?','?','?']
  283. end
  284. un = CGI.escapeHTML(v[0])
  285. if pm == 1
  286. un = %Q(<a href="reveal.rb?#{CGI.escape(pn)}/#{CGI.escape(v[0])}_#{v[3].to_i}&#{ext}">#{un != '' ? un : '_'}</a>)
  287. end
  288. date = v[3].strftime('%y/%m/%d %H:%M:%S')
  289. if v[5] == 1
  290. date = %Q(<em class="pm">#{date}</em>)
  291. end
  292. ret += "<tr><td>#{i+1}</td><td>#{un}</td><td>#{v[1]}</td><td>#{"%.4f"%v[2]}</td><td>#{date}</td><td>#{st.map{|x|"#{x}B"}*' / '}</td>"
  293. if del
  294. del[1].call(v)
  295. end
  296. ret += %Q(</tr>)
  297. end
  298. ret + %Q(</table>)
  299. end
  300. def sorted_langs
  301. file_types.zip(file_langs).sort_by{|a|a[1].downcase}
  302. end
  303. def put_by_languages(pa, base, overall = [])
  304. puts "<p>|"
  305. types = overall + sorted_langs
  306. types.each do |la, ln|
  307. if pa == la
  308. puts ln, "|"
  309. else
  310. l = la.empty? ? base : "#{base}?#{la}"
  311. puts a(l, ln), "|"
  312. end
  313. end
  314. puts "</p>"
  315. end
  316. def ranking(pn, del, pm)
  317. ret=''
  318. lr = {}
  319. ret += tag('h2', 'Ranking')
  320. ldb = PStore.new("db/#{pn}/_ranks.db")
  321. ldb.transaction(true) do
  322. file_types.zip(file_langs).each do |ext, lang|
  323. next if !ldb.root?(ext)
  324. ret += tag('h3', a(lang_url(ext),lang)+' '+a("##{lang}",'_',"#{lang}"))
  325. l = ldb[ext]
  326. next if l.empty?
  327. min = l.sort{|a,b|a[1]<=>b[1]}[0]
  328. lr[ext] = [min[1],min[3],min[0]]
  329. #lr[ext] = [l[0][1],l[0][0]]
  330. ret += lranking(ext, l, pn, del, pm)
  331. end
  332. end
  333. [ret, lr]
  334. end
  335. def read_multipart(boundary, content_length)
  336. params = Hash.new([])
  337. boundary = "--" + boundary
  338. quoted_boundary = Regexp.quote(boundary, "n")
  339. buf = ""
  340. bufsize = 10 * 1024
  341. # start multipart/form-data
  342. @i.binmode if defined? @i.binmode
  343. boundary_size = boundary.size + @@eol.size
  344. content_length -= boundary_size
  345. status = @i.read(boundary_size)
  346. if nil == status
  347. raise EOFError, "no content body"
  348. elsif boundary + @@eol != status
  349. raise EOFError, "bad content body"
  350. end
  351. loop do
  352. head = nil
  353. if 10240 < content_length
  354. require "tempfile"
  355. body = Tempfile.new("CGI")
  356. else
  357. begin
  358. require "stringio"
  359. body = StringIO.new
  360. rescue LoadError
  361. require "tempfile"
  362. body = Tempfile.new("CGI")
  363. end
  364. end
  365. body.binmode if defined? body.binmode
  366. until head and /#{quoted_boundary}(?:#{@@eol}|--)/n.match(buf)
  367. if (not head) and /#{@@eol}#{@@eol}/n.match(buf)
  368. buf = buf.sub(/\A((?:.|\n)*?#{@@eol})#{@@eol}/n) do
  369. head = $1.dup
  370. ""
  371. end
  372. next
  373. end
  374. if head and ( (@@eol + boundary + @@eol).size < buf.size )
  375. body.print buf[0 ... (buf.size - (@@eol + boundary + @@eol).size)]
  376. buf[0 ... (buf.size - (@@eol + boundary + @@eol).size)] = ""
  377. end
  378. c = if bufsize < content_length
  379. @i.read(bufsize)
  380. else
  381. @i.read(content_length)
  382. end
  383. if c.nil? || c.empty?
  384. raise EOFError, "bad content body"
  385. end
  386. buf.concat(c)
  387. content_length -= c.size
  388. end
  389. buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
  390. body.print $1
  391. if "--" == $2
  392. content_length = -1
  393. end
  394. ""
  395. end
  396. body.rewind
  397. /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
  398. filename = ($1 or "")
  399. if /Mac/ni.match(@e['HTTP_USER_AGENT']) and
  400. /Mozilla/ni.match(@e['HTTP_USER_AGENT']) and
  401. (not /MSIE/ni.match(@e['HTTP_USER_AGENT']))
  402. filename = CGI::unescape(filename)
  403. end
  404. /Content-Type: (.*)/ni.match(head)
  405. content_type = ($1 or "")
  406. (class << body; self; end).class_eval do
  407. alias local_path path
  408. define_method(:original_filename) {filename.dup.taint}
  409. define_method(:content_type) {content_type.dup.taint}
  410. end
  411. /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
  412. name = $1.dup
  413. if params.has_key?(name)
  414. params[name].push(body)
  415. else
  416. params[name] = [body]
  417. end
  418. break if buf.size == 0
  419. break if content_length === -1
  420. end
  421. params
  422. end # read_multipart
  423. def execute
  424. begin
  425. return TCPSocket.open(@@serv, @@port)
  426. rescue
  427. if @@maintenance
  428. puts %Q(now maintenance. it will be back soon. please try again later.)
  429. else
  430. puts %Q(something are broken. please send an email to shinichiro.hamaji@gmail.com if this issue stays long)
  431. end
  432. raise
  433. end
  434. end
  435. def execute2(filename, code, inputs, testing=false)
  436. modified_inputs = inputs.map do |input|
  437. if /\.sed$/ =~ filename && (!input || input.size == 0)
  438. input = "\n"
  439. end
  440. if input
  441. input.gsub!("\r\n","\n")
  442. else
  443. input = ''
  444. end
  445. input
  446. end
  447. payload = {
  448. :filename => filename,
  449. :code => code,
  450. :inputs => modified_inputs,
  451. }
  452. if testing
  453. payload[:testing] = true
  454. end
  455. encoded_payload = Marshal.dump(payload)
  456. s = execute
  457. s.puts(encoded_payload.size)
  458. s.print(encoded_payload)
  459. s.close_write
  460. s
  461. end
  462. end