Guest User

Untitled

a guest
May 6th, 2016
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 75.85 KB | None | 0 0
  1.  
  2. --
  3. -- Firewolf
  4. -- Made by GravityScore and 1lann
  5. --
  6.  
  7.  
  8.  
  9. -- Variables
  10.  
  11.  
  12. local version = "3.5"
  13. local build = 18
  14.  
  15. local w, h = term.getSize()
  16.  
  17. local isMenubarOpen = true
  18. local menubarWindow = nil
  19.  
  20. local allowUnencryptedConnections = true
  21. local enableTabBar = true
  22.  
  23. local currentWebsiteURL = ""
  24. local builtInSites = {}
  25.  
  26. local currentProtocol = ""
  27. local protocols = {}
  28.  
  29. local currentTab = 1
  30. local maxTabs = 5
  31. local maxTabNameWidth = 8
  32. local tabs = {}
  33.  
  34. local languages = {}
  35.  
  36. local history = {}
  37.  
  38. local publicDNSChannel = 9999
  39. local publicResponseChannel = 9998
  40. local responseID = 41738
  41.  
  42. local httpTimeout = 10
  43. local searchResultTimeout = 1
  44. local initiationTimeout = 2
  45. local animationInterval = 0.125
  46. local fetchTimeout = 3
  47. local serverLimitPerComputer = 1
  48.  
  49. local websiteErrorEvent = "firewolf_websiteErrorEvent"
  50. local redirectEvent = "firewolf_redirectEvent"
  51.  
  52. local baseURL = "https://raw.githubusercontent.com/1lann/Firewolf/master/src"
  53. local buildURL = baseURL .. "/build.txt"
  54. local firewolfURL = baseURL .. "/client.lua"
  55. local serverURL = baseURL .. "/server.lua"
  56.  
  57. local originalTerminal = term.current()
  58.  
  59. local firewolfLocation = "/" .. shell.getRunningProgram()
  60. local downloadsLocation = "/downloads"
  61.  
  62.  
  63. local theme = {}
  64.  
  65. local colorTheme = {
  66. background = colors.gray,
  67. accent = colors.red,
  68. subtle = colors.orange,
  69.  
  70. lightText = colors.gray,
  71. text = colors.white,
  72. errorText = colors.red,
  73. }
  74.  
  75. local grayscaleTheme = {
  76. background = colors.black,
  77. accent = colors.black,
  78. subtle = colors.black,
  79.  
  80. lightText = colors.white,
  81. text = colors.white,
  82. errorText = colors.white,
  83. }
  84.  
  85.  
  86.  
  87. -- Utilities
  88.  
  89.  
  90. local modifiedRead = function(properties)
  91. local text = ""
  92. local startX, startY = term.getCursorPos()
  93. local pos = 0
  94.  
  95. local previousText = ""
  96. local readHistory = nil
  97. local historyPos = 0
  98.  
  99. if not properties then
  100. properties = {}
  101. end
  102.  
  103. if properties.displayLength then
  104. properties.displayLength = math.min(properties.displayLength, w - 2)
  105. else
  106. properties.displayLength = w - startX - 1
  107. end
  108.  
  109. if properties.startingText then
  110. text = properties.startingText
  111. pos = text:len()
  112. end
  113.  
  114. if properties.history then
  115. readHistory = {}
  116. for k, v in pairs(properties.history) do
  117. readHistory[k] = v
  118. end
  119. end
  120.  
  121. if readHistory[1] == text then
  122. table.remove(readHistory, 1)
  123. end
  124.  
  125. local draw = function(replaceCharacter)
  126. local scroll = 0
  127. if properties.displayLength and pos > properties.displayLength then
  128. scroll = pos - properties.displayLength
  129. end
  130.  
  131. local repl = replaceCharacter or properties.replaceCharacter
  132. term.setTextColor(theme.text)
  133. term.setCursorPos(startX, startY)
  134. if repl then
  135. term.write(string.rep(repl:sub(1, 1), text:len() - scroll))
  136. else
  137. term.write(text:sub(scroll + 1))
  138. end
  139.  
  140. term.setCursorPos(startX + pos - scroll, startY)
  141. end
  142.  
  143. term.setCursorBlink(true)
  144. draw()
  145. while true do
  146. local event, key, x, y, param4, param5 = os.pullEvent()
  147.  
  148. if properties.onEvent then
  149. -- Actions:
  150. -- - exit (bool)
  151. -- - text
  152. -- - nullifyText
  153.  
  154. term.setCursorBlink(false)
  155. local action = properties.onEvent(text, event, key, x, y, param4, param5)
  156. if action then
  157. if action.text then
  158. draw(" ")
  159. text = action.text
  160. pos = text:len()
  161. end if action.nullifyText then
  162. text = nil
  163. action.exit = true
  164. end if action.exit then
  165. break
  166. end
  167. end
  168. draw()
  169. end
  170.  
  171. term.setCursorBlink(true)
  172. if event == "char" then
  173. local canType = true
  174. if properties.maxLength and text:len() >= properties.maxLength then
  175. canType = false
  176. end
  177.  
  178. if canType then
  179. text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1)
  180. pos = pos + 1
  181. draw()
  182. end
  183. elseif event == "key" then
  184. if key == keys.enter then
  185. break
  186. elseif key == keys.left and pos > 0 then
  187. pos = pos - 1
  188. draw()
  189. elseif key == keys.right and pos < text:len() then
  190. pos = pos + 1
  191. draw()
  192. elseif key == keys.backspace and pos > 0 then
  193. draw(" ")
  194. text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1)
  195. pos = pos - 1
  196. draw()
  197. elseif key == keys.delete and pos < text:len() then
  198. draw(" ")
  199. text = text:sub(1, pos) .. text:sub(pos + 2, -1)
  200. draw()
  201. elseif key == keys.home then
  202. pos = 0
  203. draw()
  204. elseif key == keys["end"] then
  205. pos = text:len()
  206. draw()
  207. elseif (key == keys.up or key == keys.down) and readHistory then
  208. local shouldDraw = false
  209. if historyPos == 0 then
  210. previousText = text
  211. elseif historyPos > 0 then
  212. readHistory[historyPos] = text
  213. end
  214.  
  215. if key == keys.up then
  216. if historyPos < #readHistory then
  217. historyPos = historyPos + 1
  218. shouldDraw = true
  219. end
  220. else
  221. if historyPos > 0 then
  222. historyPos = historyPos - 1
  223. shouldDraw = true
  224. end
  225. end
  226.  
  227. if shouldDraw then
  228. draw(" ")
  229. if historyPos > 0 then
  230. text = readHistory[historyPos]
  231. else
  232. text = previousText
  233. end
  234. pos = text:len()
  235. draw()
  236. end
  237. end
  238. elseif event == "mouse_click" then
  239. local scroll = 0
  240. if properties.displayLength and pos > properties.displayLength then
  241. scroll = pos - properties.displayLength
  242. end
  243.  
  244. if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  245. pos = x - startX + scroll
  246. draw()
  247. elseif y == startY then
  248. if x < startX then
  249. pos = scroll
  250. draw()
  251. elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  252. pos = text:len()
  253. draw()
  254. end
  255. end
  256. end
  257. end
  258.  
  259. term.setCursorBlink(false)
  260. print("")
  261. return text
  262. end
  263.  
  264.  
  265. local prompt = function(items, x, y, w, h)
  266. local selected = 1
  267. local scroll = 0
  268.  
  269. local draw = function()
  270. for i = scroll + 1, scroll + h do
  271. local item = items[i]
  272. if item then
  273. term.setCursorPos(x, y + i - 1)
  274. term.setBackgroundColor(theme.background)
  275. term.setTextColor(theme.lightText)
  276.  
  277. if scroll + selected == i then
  278. term.setTextColor(theme.text)
  279. term.write(" > ")
  280. else
  281. term.write(" - ")
  282. end
  283.  
  284. term.write(item)
  285. end
  286. end
  287. end
  288.  
  289. draw()
  290. while true do
  291. local event, key, x, y = os.pullEvent()
  292.  
  293. if event == "key" then
  294. if key == keys.up and selected > 1 then
  295. selected = selected - 1
  296.  
  297. if selected - scroll == 0 then
  298. scroll = scroll - 1
  299. end
  300. elseif key == keys.down and selected < #items then
  301. selected = selected + 1
  302. end
  303.  
  304. draw()
  305. elseif event == "mouse_click" then
  306.  
  307. elseif event == "mouse_scroll" then
  308. if key > 0 then
  309. os.queueEvent("key", keys.down)
  310. else
  311. os.queueEvent("key", keys.up)
  312. end
  313. end
  314. end
  315. end
  316.  
  317.  
  318.  
  319. -- GUI
  320.  
  321.  
  322. local clear = function(bg, fg)
  323. term.setTextColor(fg)
  324. term.setBackgroundColor(bg)
  325. term.clear()
  326. term.setCursorPos(1, 1)
  327. end
  328.  
  329.  
  330. local fill = function(x, y, width, height, bg)
  331. term.setBackgroundColor(bg)
  332. for i = y, y + height - 1 do
  333. term.setCursorPos(x, i)
  334. term.write(string.rep(" ", width))
  335. end
  336. end
  337.  
  338.  
  339. local center = function(text)
  340. local x, y = term.getCursorPos()
  341. term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  342. term.write(text)
  343. term.setCursorPos(1, y + 1)
  344. end
  345.  
  346.  
  347. local centerSplit = function(text, width)
  348. local words = {}
  349. for word in text:gmatch("[^ \t]+") do
  350. table.insert(words, word)
  351. end
  352.  
  353. local lines = {""}
  354. while lines[#lines]:len() < width do
  355. lines[#lines] = lines[#lines] .. words[1] .. " "
  356. table.remove(words, 1)
  357.  
  358. if #words == 0 then
  359. break
  360. end
  361.  
  362. if lines[#lines]:len() + words[1]:len() >= width then
  363. table.insert(lines, "")
  364. end
  365. end
  366.  
  367. for _, line in pairs(lines) do
  368. center(line)
  369. end
  370. end
  371.  
  372.  
  373.  
  374. -- Updating
  375.  
  376.  
  377. local download = function(url)
  378. http.request(url)
  379. local timeoutID = os.startTimer(httpTimeout)
  380. while true do
  381. local event, fetchedURL, response = os.pullEvent()
  382. if (event == "timer" and fetchedURL == timeoutID) or event == "http_failure" then
  383. return false
  384. elseif event == "http_success" and fetchedURL == url then
  385. local contents = response.readAll()
  386. response.close()
  387. return contents
  388. end
  389. end
  390. end
  391.  
  392.  
  393. local downloadAndSave = function(url, path)
  394. local contents = download(url)
  395. if contents and not fs.isReadOnly(path) and not fs.isDir(path) then
  396. local f = io.open(path, "w")
  397. f:write(contents)
  398. f:close()
  399. return false
  400. end
  401. return true
  402. end
  403.  
  404.  
  405. local updateAvailable = function()
  406. local number = download(buildURL)
  407. if not number then
  408. return false, true
  409. end
  410.  
  411. if number and tonumber(number) and tonumber(number) > build then
  412. return true, false
  413. end
  414.  
  415. return false, false
  416. end
  417.  
  418.  
  419. local redownloadBrowser = function()
  420. return downloadAndSave(firewolfURL, firewolfLocation)
  421. end
  422.  
  423.  
  424.  
  425. -- Display Websites
  426.  
  427.  
  428. builtInSites["display"] = {}
  429.  
  430.  
  431. builtInSites["display"]["firewolf"] = function()
  432. local logo = {
  433. "______ _ __ ",
  434. "| ___| | |/ _|",
  435. "| |_ _ ____ _____ _____ | | |_ ",
  436. "| _|| | __/ _ \\ \\ /\\ / / _ \\| | _|",
  437. "| | | | | | __/\\ V V / <_> | | | ",
  438. "\\_| |_|_| \\___| \\_/\\_/ \\___/|_|_| ",
  439. }
  440.  
  441. clear(theme.background, theme.text)
  442. fill(1, 3, w, 9, theme.subtle)
  443.  
  444. term.setCursorPos(1, 3)
  445. for _, line in pairs(logo) do
  446. center(line)
  447. end
  448.  
  449. term.setCursorPos(1, 10)
  450. center(version)
  451.  
  452. term.setBackgroundColor(theme.background)
  453. term.setTextColor(theme.text)
  454. term.setCursorPos(1, 14)
  455. center("Search using the Query Box above")
  456. center("Visit rdnt://help for help using Firewolf.")
  457.  
  458. term.setCursorPos(1, h - 2)
  459. center("Made by GravityScore and 1lann")
  460. end
  461.  
  462.  
  463. builtInSites["display"]["credits"] = function()
  464. clear(theme.background, theme.text)
  465.  
  466. fill(1, 6, w, 3, theme.subtle)
  467. term.setCursorPos(1, 7)
  468. center("Credits")
  469.  
  470. term.setBackgroundColor(theme.background)
  471. term.setCursorPos(1, 11)
  472. center("Written by GravityScore and 1lann")
  473. print("")
  474. center("RC4 Implementation by AgentE382")
  475. end
  476.  
  477.  
  478. builtInSites["display"]["help"] = function()
  479. clear(theme.background, theme.text)
  480.  
  481. fill(1, 3, w, 3, theme.subtle)
  482. term.setCursorPos(1, 4)
  483. center("Help")
  484.  
  485. term.setBackgroundColor(theme.background)
  486. term.setCursorPos(1, 7)
  487. center("Click on the URL bar or press control to")
  488. center("open the query box")
  489. print("")
  490. center("Type in a search query or website URL")
  491. center("into the query box.")
  492. print("")
  493. center("Search for nothing to see all available")
  494. center("websites.")
  495. print("")
  496. center("Visit rdnt://server to setup a server.")
  497. center("Visit rdnt://update to update Firewolf.")
  498. end
  499.  
  500.  
  501. builtInSites["display"]["server"] = function()
  502. clear(theme.background, theme.text)
  503.  
  504. fill(1, 6, w, 3, theme.subtle)
  505. term.setCursorPos(1, 7)
  506. center("Server Software")
  507.  
  508. term.setBackgroundColor(theme.background)
  509. term.setCursorPos(1, 11)
  510. if not http then
  511. center("HTTP is not enabled!")
  512. print("")
  513. center("Please enable it in your config file")
  514. center("to download Firewolf Server.")
  515. else
  516. center("Press space to download")
  517. center("Firewolf Server to:")
  518. print("")
  519. center("/fwserver")
  520.  
  521. while true do
  522. local event, key = os.pullEvent()
  523. if event == "key" and key == 57 then
  524. fill(1, 11, w, 4, theme.background)
  525. term.setCursorPos(1, 11)
  526. center("Downloading...")
  527.  
  528. local err = downloadAndSave(serverURL, "/fwserver")
  529.  
  530. fill(1, 11, w, 4, theme.background)
  531. term.setCursorPos(1, 11)
  532. center(err and "Download failed!" or "Download successful!")
  533. end
  534. end
  535. end
  536. end
  537.  
  538.  
  539. builtInSites["display"]["update"] = function()
  540. clear(theme.background, theme.text)
  541.  
  542. fill(1, 3, w, 3, theme.subtle)
  543. term.setCursorPos(1, 4)
  544. center("Update")
  545.  
  546. term.setBackgroundColor(theme.background)
  547. if not http then
  548. term.setCursorPos(1, 9)
  549. center("HTTP is not enabled!")
  550. print("")
  551. center("Please enable it in your config")
  552. center("file to download Firewolf updates.")
  553. else
  554. term.setCursorPos(1, 10)
  555. center("Checking for updates...")
  556.  
  557. local available, err = updateAvailable()
  558.  
  559. term.setCursorPos(1, 10)
  560. if available then
  561. term.clearLine()
  562. center("Update found!")
  563. center("Press enter to download.")
  564.  
  565. while true do
  566. local event, key = os.pullEvent()
  567. if event == "key" and key == keys.enter then
  568. break
  569. end
  570. end
  571.  
  572. fill(1, 10, w, 2, theme.background)
  573. term.setCursorPos(1, 10)
  574. center("Downloading...")
  575.  
  576. local err = redownloadBrowser()
  577.  
  578. term.setCursorPos(1, 10)
  579. term.clearLine()
  580. if err then
  581. center("Download failed!")
  582. else
  583. center("Download succeeded!")
  584. center("Please restart Firewolf...")
  585. end
  586. elseif err then
  587. term.clearLine()
  588. center("Checking failed!")
  589. else
  590. term.clearLine()
  591. center("No updates found.")
  592. end
  593. end
  594. end
  595.  
  596.  
  597.  
  598. -- Built In Websites
  599.  
  600.  
  601. builtInSites["error"] = function(err)
  602. fill(1, 3, w, 3, theme.subtle)
  603. term.setCursorPos(1, 4)
  604. center("Failed to load page!")
  605.  
  606. term.setBackgroundColor(theme.background)
  607. term.setCursorPos(1, 9)
  608. center(err)
  609. print("")
  610. center("Please try again.")
  611. end
  612.  
  613.  
  614. builtInSites["noresults"] = function()
  615. fill(1, 3, w, 3, theme.subtle)
  616. term.setCursorPos(1, 4)
  617. center("No results!")
  618.  
  619. term.setBackgroundColor(theme.background)
  620. term.setCursorPos(1, 9)
  621. center("Your search didn't return")
  622. center("any results!")
  623.  
  624. os.pullEvent("key")
  625. os.queueEvent("")
  626. os.pullEvent()
  627. end
  628.  
  629.  
  630. builtInSites["search advanced"] = function(results)
  631. local startY = 6
  632. local height = h - startY - 1
  633. local scroll = 0
  634.  
  635. local draw = function()
  636. fill(1, startY, w, height + 1, theme.background)
  637.  
  638. for i = scroll + 1, scroll + height do
  639. if results[i] then
  640. term.setCursorPos(5, (i - scroll) + startY)
  641. term.write(currentProtocol .. "://" .. results[i])
  642. end
  643. end
  644. end
  645.  
  646. draw()
  647. while true do
  648. local event, but, x, y = os.pullEvent()
  649.  
  650. if event == "mouse_click" and y >= startY and y <= startY + height then
  651. local item = results[y - startY + scroll]
  652. if item then
  653. os.queueEvent(redirectEvent, item)
  654. coroutine.yield()
  655. end
  656. elseif event == "key" then
  657. if but == keys.up then
  658. scroll = math.max(0, scroll - 1)
  659. elseif but == keys.down and #results > height then
  660. scroll = math.min(scroll + 1, #results - height)
  661. end
  662.  
  663. draw()
  664. elseif event == "mouse_scroll" then
  665. if but > 0 then
  666. os.queueEvent("key", keys.down)
  667. else
  668. os.queueEvent("key", keys.up)
  669. end
  670. end
  671. end
  672. end
  673.  
  674.  
  675. builtInSites["search basic"] = function(results)
  676. local startY = 6
  677. local height = h - startY - 1
  678. local scroll = 0
  679. local selected = 1
  680.  
  681. local draw = function()
  682. fill(1, startY, w, height + 1, theme.background)
  683.  
  684. for i = scroll + 1, scroll + height do
  685. if results[i] then
  686. if i == selected + scroll then
  687. term.setCursorPos(3, (i - scroll) + startY)
  688. term.write("> " .. currentProtocol .. "://" .. results[i])
  689. else
  690. term.setCursorPos(5, (i - scroll) + startY)
  691. term.write(currentProtocol .. "://" .. results[i])
  692. end
  693. end
  694. end
  695. end
  696.  
  697. draw()
  698. while true do
  699. local event, but, x, y = os.pullEvent()
  700.  
  701. if event == "key" then
  702. if but == keys.up and selected + scroll > 1 then
  703. if selected > 1 then
  704. selected = selected - 1
  705. else
  706. scroll = math.max(0, scroll - 1)
  707. end
  708. elseif but == keys.down and selected + scroll < #results then
  709. if selected < height then
  710. selected = selected + 1
  711. else
  712. scroll = math.min(scroll + 1, #results - height)
  713. end
  714. elseif but == keys.enter then
  715. local item = results[scroll + selected]
  716. if item then
  717. os.queueEvent(redirectEvent, item)
  718. coroutine.yield()
  719. end
  720. end
  721.  
  722. draw()
  723. elseif event == "mouse_scroll" then
  724. if but > 0 then
  725. os.queueEvent("key", keys.down)
  726. else
  727. os.queueEvent("key", keys.up)
  728. end
  729. end
  730. end
  731. end
  732.  
  733.  
  734. builtInSites["search"] = function(results)
  735. clear(theme.background, theme.text)
  736.  
  737. fill(1, 3, w, 3, theme.subtle)
  738. term.setCursorPos(1, 4)
  739. center(#results .. " Search " .. (#results == 1 and "Result" or "Results"))
  740.  
  741. term.setBackgroundColor(theme.background)
  742.  
  743. if term.isColor() then
  744. builtInSites["search advanced"](results)
  745. else
  746. builtInSites["search basic"](results)
  747. end
  748. end
  749.  
  750.  
  751. builtInSites["crash"] = function(err)
  752. fill(1, 3, w, 3, theme.subtle)
  753. term.setCursorPos(1, 4)
  754. center("The website crashed!")
  755.  
  756. term.setBackgroundColor(theme.background)
  757. term.setCursorPos(1, 8)
  758. centerSplit(err, w - 4)
  759. print("\n")
  760. center("Please report this error to")
  761. center("the website creator.")
  762. end
  763.  
  764.  
  765.  
  766. -- Menubar
  767.  
  768.  
  769. local getTabName = function(url)
  770. local name = url:match("^[^/]+")
  771.  
  772. if not name then
  773. name = "Search"
  774. end
  775.  
  776. if name:sub(1, 3) == "www" then
  777. name = name:sub(5):gsub("^%s*(.-)%s*$", "%1")
  778. end
  779.  
  780. if name:len() > maxTabNameWidth then
  781. name = name:sub(1, maxTabNameWidth):gsub("^%s*(.-)%s*$", "%1")
  782. end
  783.  
  784. if name:sub(-1, -1) == "." then
  785. name = name:sub(1, -2):gsub("^%s*(.-)%s*$", "%1")
  786. end
  787.  
  788. return name:gsub("^%s*(.-)%s*$", "%1")
  789. end
  790.  
  791.  
  792. local determineClickedTab = function(x, y)
  793. if y == 2 then
  794. local minx = 2
  795. for i, tab in pairs(tabs) do
  796. local name = getTabName(tab.url)
  797.  
  798. if x >= minx and x <= minx + name:len() - 1 then
  799. return i
  800. elseif x == minx + name:len() and i == currentTab and #tabs > 1 then
  801. return "close"
  802. else
  803. minx = minx + name:len() + 2
  804. end
  805. end
  806.  
  807. if x == minx and #tabs < maxTabs then
  808. return "new"
  809. end
  810. end
  811.  
  812. return nil
  813. end
  814.  
  815.  
  816. local setupMenubar = function()
  817. if enableTabBar then
  818. menubarWindow = window.create(originalTerminal, 1, 1, w, 2, false)
  819. else
  820. menubarWindow = window.create(originalTerminal, 1, 1, w, 1, false)
  821. end
  822. end
  823.  
  824.  
  825. local drawMenubar = function()
  826. if isMenubarOpen then
  827. term.redirect(menubarWindow)
  828. menubarWindow.setVisible(true)
  829.  
  830. fill(1, 1, w, 1, theme.accent)
  831. term.setTextColor(theme.text)
  832.  
  833. term.setBackgroundColor(theme.accent)
  834. term.setCursorPos(2, 1)
  835. if currentWebsiteURL:match("^[^%?]+") then
  836. term.write(currentProtocol .. "://" .. currentWebsiteURL:match("^[^%?]+"))
  837. else
  838. term.write(currentProtocol .. "://" ..currentWebsiteURL)
  839. end
  840.  
  841. term.setCursorPos(w - 5, 1)
  842. term.write("[===]")
  843.  
  844. if enableTabBar then
  845. fill(1, 2, w, 1, theme.subtle)
  846.  
  847. term.setCursorPos(1, 2)
  848. for i, tab in pairs(tabs) do
  849. term.setBackgroundColor(theme.subtle)
  850. term.setTextColor(theme.lightText)
  851. if i == currentTab then
  852. term.setTextColor(theme.text)
  853. end
  854.  
  855. local tabName = getTabName(tab.url)
  856. term.write(" " .. tabName)
  857.  
  858. if i == currentTab and #tabs > 1 then
  859. term.setTextColor(theme.errorText)
  860. term.write("x")
  861. else
  862. term.write(" ")
  863. end
  864. end
  865.  
  866. if #tabs < maxTabs then
  867. term.setTextColor(theme.lightText)
  868. term.setBackgroundColor(theme.subtle)
  869. term.write(" + ")
  870. end
  871. end
  872. else
  873. menubarWindow.setVisible(false)
  874. end
  875. end
  876.  
  877.  
  878.  
  879. -- RC4
  880. -- Implementation by AgentE382
  881.  
  882.  
  883. local cryptWrapper = function(plaintext, salt)
  884. local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)}
  885. local S = {}
  886. for i = 0, 255 do
  887. S[i] = i
  888. end
  889.  
  890. local j, keylength = 0, #key
  891. for i = 0, 255 do
  892. j = (j + S[i] + key[i % keylength + 1]) % 256
  893. S[i], S[j] = S[j], S[i]
  894. end
  895.  
  896. local i = 0
  897. j = 0
  898. local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false
  899.  
  900. for n = 1, #chars do
  901. i = (i + 1) % 256
  902. j = (j + S[i]) % 256
  903. S[i], S[j] = S[j], S[i]
  904. chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
  905. if chars[n] > 127 or chars[n] == 13 then
  906. astable = true
  907. end
  908. end
  909.  
  910. return astable and chars or string.char(unpack(chars))
  911. end
  912.  
  913.  
  914. local crypt = function(text, key)
  915. local resp, msg = pcall(cryptWrapper, text, key)
  916. if resp then
  917. return msg
  918. else
  919. return nil
  920. end
  921. end
  922.  
  923.  
  924.  
  925. -- Base64
  926. --
  927. -- Base64 Encryption/Decryption
  928. -- By KillaVanilla
  929. -- http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/
  930. -- http://pastebin.com/rCYDnCxn
  931. --
  932.  
  933.  
  934. local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  935.  
  936.  
  937. local function sixBitToBase64(input)
  938. return string.sub(alphabet, input+1, input+1)
  939. end
  940.  
  941.  
  942. local function base64ToSixBit(input)
  943. for i=1, 64 do
  944. if input == string.sub(alphabet, i, i) then
  945. return i-1
  946. end
  947. end
  948. end
  949.  
  950.  
  951. local function octetToBase64(o1, o2, o3)
  952. local shifted = bit.brshift(bit.band(o1, 0xFC), 2)
  953. local i1 = sixBitToBase64(shifted)
  954. local i2 = "A"
  955. local i3 = "="
  956. local i4 = "="
  957. if o2 then
  958. i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) ))
  959. if not o3 then
  960. i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2))
  961. else
  962. i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) ))
  963. end
  964. else
  965. i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4))
  966. end
  967. if o3 then
  968. i4 = sixBitToBase64(bit.band(o3, 0x3F))
  969. end
  970.  
  971. return i1..i2..i3..i4
  972. end
  973.  
  974.  
  975. local function base64ToThreeOctet(s1)
  976. local c1 = base64ToSixBit(string.sub(s1, 1, 1))
  977. local c2 = base64ToSixBit(string.sub(s1, 2, 2))
  978. local c3 = 0
  979. local c4 = 0
  980. local o1 = 0
  981. local o2 = 0
  982. local o3 = 0
  983. if string.sub(s1, 3, 3) == "=" then
  984. c3 = nil
  985. c4 = nil
  986. elseif string.sub(s1, 4, 4) == "=" then
  987. c3 = base64ToSixBit(string.sub(s1, 3, 3))
  988. c4 = nil
  989. else
  990. c3 = base64ToSixBit(string.sub(s1, 3, 3))
  991. c4 = base64ToSixBit(string.sub(s1, 4, 4))
  992. end
  993. o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) )
  994. if c3 then
  995. o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) )
  996. else
  997. o2 = nil
  998. end
  999. if c4 then
  1000. o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 )
  1001. else
  1002. o3 = nil
  1003. end
  1004. return o1, o2, o3
  1005. end
  1006.  
  1007.  
  1008. local function splitIntoBlocks(bytes)
  1009. local blockNum = 1
  1010. local blocks = {}
  1011. for i=1, #bytes, 3 do
  1012. blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
  1013. blockNum = blockNum+1
  1014. end
  1015. return blocks
  1016. end
  1017.  
  1018.  
  1019. function base64Encode(bytes)
  1020. local blocks = splitIntoBlocks(bytes)
  1021. local output = ""
  1022. for i=1, #blocks do
  1023. output = output..octetToBase64( unpack(blocks[i]) )
  1024. end
  1025. return output
  1026. end
  1027.  
  1028.  
  1029. function base64Decode(str)
  1030. local bytes = {}
  1031. local blocks = {}
  1032. local blockNum = 1
  1033.  
  1034. for i=1, #str, 4 do
  1035. blocks[blockNum] = string.sub(str, i, i+3)
  1036. blockNum = blockNum+1
  1037. end
  1038.  
  1039. for i=1, #blocks do
  1040. local o1, o2, o3 = base64ToThreeOctet(blocks[i])
  1041. table.insert(bytes, o1)
  1042. table.insert(bytes, o2)
  1043. table.insert(bytes, o3)
  1044. end
  1045.  
  1046. return bytes
  1047. end
  1048.  
  1049.  
  1050.  
  1051. -- SHA-256
  1052. --
  1053. -- Adaptation of the Secure Hashing Algorithm (SHA-244/256)
  1054. -- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
  1055. --
  1056. -- Using an adapted version of the bit library
  1057. -- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
  1058.  
  1059.  
  1060. local MOD = 2^32
  1061. local MODM = MOD-1
  1062.  
  1063.  
  1064. local function memoize(f)
  1065. local mt = {}
  1066. local t = setmetatable({}, mt)
  1067. function mt:__index(k)
  1068. local v = f(k)
  1069. t[k] = v
  1070. return v
  1071. end
  1072. return t
  1073. end
  1074.  
  1075.  
  1076. local function make_bitop_uncached(t, m)
  1077. local function bitop(a, b)
  1078. local res,p = 0,1
  1079. while a ~= 0 and b ~= 0 do
  1080. local am, bm = a % m, b % m
  1081. res = res + t[am][bm] * p
  1082. a = (a - am) / m
  1083. b = (b - bm) / m
  1084. p = p * m
  1085. end
  1086. res = res + (a + b) * p
  1087. return res
  1088. end
  1089.  
  1090. return bitop
  1091. end
  1092.  
  1093.  
  1094. local function make_bitop(t)
  1095. local op1 = make_bitop_uncached(t,2^1)
  1096. local op2 = memoize(function(a)
  1097. return memoize(function(b)
  1098. return op1(a, b)
  1099. end)
  1100. end)
  1101. return make_bitop_uncached(op2, 2 ^ (t.n or 1))
  1102. end
  1103.  
  1104.  
  1105. local customBxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})
  1106.  
  1107. local function customBxor(a, b, c, ...)
  1108. local z = nil
  1109. if b then
  1110. a = a % MOD
  1111. b = b % MOD
  1112. z = customBxor1(a, b)
  1113. if c then
  1114. z = customBxor(z, c, ...)
  1115. end
  1116. return z
  1117. elseif a then
  1118. return a % MOD
  1119. else
  1120. return 0
  1121. end
  1122. end
  1123.  
  1124.  
  1125. local function customBand(a, b, c, ...)
  1126. local z
  1127. if b then
  1128. a = a % MOD
  1129. b = b % MOD
  1130. z = ((a + b) - customBxor1(a,b)) / 2
  1131. if c then
  1132. z = customBand(z, c, ...)
  1133. end
  1134. return z
  1135. elseif a then
  1136. return a % MOD
  1137. else
  1138. return MODM
  1139. end
  1140. end
  1141.  
  1142.  
  1143. local function bnot(x)
  1144. return (-1 - x) % MOD
  1145. end
  1146.  
  1147.  
  1148. local function rshift1(a, disp)
  1149. if disp < 0 then
  1150. return lshift(a, -disp)
  1151. end
  1152. return math.floor(a % 2 ^ 32 / 2 ^ disp)
  1153. end
  1154.  
  1155.  
  1156. local function rshift(x, disp)
  1157. if disp > 31 or disp < -31 then
  1158. return 0
  1159. end
  1160. return rshift1(x % MOD, disp)
  1161. end
  1162.  
  1163.  
  1164. local function lshift(a, disp)
  1165. if disp < 0 then
  1166. return rshift(a, -disp)
  1167. end
  1168. return (a * 2 ^ disp) % 2 ^ 32
  1169. end
  1170.  
  1171.  
  1172. local function rrotate(x, disp)
  1173. x = x % MOD
  1174. disp = disp % 32
  1175. local low = customBand(x, 2 ^ disp - 1)
  1176. return rshift(x, disp) + lshift(low, 32 - disp)
  1177. end
  1178.  
  1179.  
  1180. local k = {
  1181. 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
  1182. 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  1183. 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
  1184. 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  1185. 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
  1186. 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  1187. 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
  1188. 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  1189. 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  1190. 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  1191. 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
  1192. 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  1193. 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
  1194. 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  1195. 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
  1196. 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  1197. }
  1198.  
  1199.  
  1200. local function str2hexa(s)
  1201. return (string.gsub(s, ".", function(c)
  1202. return string.format("%02x", string.byte(c))
  1203. end))
  1204. end
  1205.  
  1206.  
  1207. local function num2s(l, n)
  1208. local s = ""
  1209. for i = 1, n do
  1210. local rem = l % 256
  1211. s = string.char(rem) .. s
  1212. l = (l - rem) / 256
  1213. end
  1214. return s
  1215. end
  1216.  
  1217.  
  1218. local function s232num(s, i)
  1219. local n = 0
  1220. for i = i, i + 3 do
  1221. n = n*256 + string.byte(s, i)
  1222. end
  1223. return n
  1224. end
  1225.  
  1226.  
  1227. local function preproc(msg, len)
  1228. local extra = 64 - ((len + 9) % 64)
  1229. len = num2s(8 * len, 8)
  1230. msg = msg .. "\128" .. string.rep("\0", extra) .. len
  1231. assert(#msg % 64 == 0)
  1232. return msg
  1233. end
  1234.  
  1235.  
  1236. local function initH256(H)
  1237. H[1] = 0x6a09e667
  1238. H[2] = 0xbb67ae85
  1239. H[3] = 0x3c6ef372
  1240. H[4] = 0xa54ff53a
  1241. H[5] = 0x510e527f
  1242. H[6] = 0x9b05688c
  1243. H[7] = 0x1f83d9ab
  1244. H[8] = 0x5be0cd19
  1245. return H
  1246. end
  1247.  
  1248.  
  1249. local function digestblock(msg, i, H)
  1250. local w = {}
  1251. for j = 1, 16 do
  1252. w[j] = s232num(msg, i + (j - 1)*4)
  1253. end
  1254. for j = 17, 64 do
  1255. local v = w[j - 15]
  1256. local s0 = customBxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
  1257. v = w[j - 2]
  1258. w[j] = w[j - 16] + s0 + w[j - 7] + customBxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
  1259. end
  1260.  
  1261. local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
  1262. for i = 1, 64 do
  1263. local s0 = customBxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
  1264. local maj = customBxor(customBand(a, b), customBand(a, c), customBand(b, c))
  1265. local t2 = s0 + maj
  1266. local s1 = customBxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
  1267. local ch = customBxor (customBand(e, f), customBand(bnot(e), g))
  1268. local t1 = h + s1 + ch + k[i] + w[i]
  1269. h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
  1270. end
  1271.  
  1272. H[1] = customBand(H[1] + a)
  1273. H[2] = customBand(H[2] + b)
  1274. H[3] = customBand(H[3] + c)
  1275. H[4] = customBand(H[4] + d)
  1276. H[5] = customBand(H[5] + e)
  1277. H[6] = customBand(H[6] + f)
  1278. H[7] = customBand(H[7] + g)
  1279. H[8] = customBand(H[8] + h)
  1280. end
  1281.  
  1282.  
  1283. local function sha256(msg)
  1284. msg = preproc(msg, #msg)
  1285. local H = initH256({})
  1286. for i = 1, #msg, 64 do
  1287. digestblock(msg, i, H)
  1288. end
  1289. return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
  1290. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
  1291. end
  1292.  
  1293.  
  1294. local protocolName = "Firewolf"
  1295.  
  1296.  
  1297.  
  1298. -- Cryptography
  1299.  
  1300.  
  1301. local Cryptography = {}
  1302. Cryptography.sha = {}
  1303. Cryptography.base64 = {}
  1304. Cryptography.aes = {}
  1305.  
  1306.  
  1307. function Cryptography.bytesFromMessage(msg)
  1308. local bytes = {}
  1309.  
  1310. for i = 1, msg:len() do
  1311. local letter = string.byte(msg:sub(i, i))
  1312. table.insert(bytes, letter)
  1313. end
  1314.  
  1315. return bytes
  1316. end
  1317.  
  1318.  
  1319. function Cryptography.messageFromBytes(bytes)
  1320. local msg = ""
  1321.  
  1322. for i = 1, #bytes do
  1323. local letter = string.char(bytes[i])
  1324. msg = msg .. letter
  1325. end
  1326.  
  1327. return msg
  1328. end
  1329.  
  1330.  
  1331. function Cryptography.bytesFromKey(key)
  1332. local bytes = {}
  1333.  
  1334. for i = 1, key:len() / 2 do
  1335. local group = key:sub((i - 1) * 2 + 1, (i - 1) * 2 + 1)
  1336. local num = tonumber(group, 16)
  1337. table.insert(bytes, num)
  1338. end
  1339.  
  1340. return bytes
  1341. end
  1342.  
  1343.  
  1344. function Cryptography.sha.sha256(msg)
  1345. return sha256(msg)
  1346. end
  1347.  
  1348.  
  1349. function Cryptography.aes.encrypt(msg, key)
  1350. local thing = crypt(msg, key)
  1351. if not thing then
  1352. error(thing)
  1353. end
  1354. return base64Encode(thing)
  1355. end
  1356.  
  1357.  
  1358. function Cryptography.aes.decrypt(msg, key)
  1359. return crypt(base64Decode(msg), key)
  1360. end
  1361.  
  1362.  
  1363. function Cryptography.base64.encode(msg)
  1364. if not msg then
  1365. error(msg)
  1366. end
  1367. return base64Encode(Cryptography.bytesFromMessage(msg))
  1368. end
  1369.  
  1370.  
  1371. function Cryptography.base64.decode(msg)
  1372. return Cryptography.messageFromBytes(base64Decode(msg))
  1373. end
  1374.  
  1375.  
  1376. function Cryptography.channel(text)
  1377. local hashed = Cryptography.sha.sha256(text)
  1378.  
  1379. local total = 0
  1380.  
  1381. for i = 1, hashed:len() do
  1382. total = total + string.byte(hashed:sub(i, i))
  1383. end
  1384.  
  1385. return (total % 55530) + 10000
  1386. end
  1387.  
  1388.  
  1389. function Cryptography.sanatize(text)
  1390. local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"}
  1391.  
  1392. for _, char in pairs(sanatizeChars) do
  1393. text = text:gsub("%"..char, "%%%"..char)
  1394. end
  1395. return text
  1396. end
  1397.  
  1398.  
  1399.  
  1400. -- Modem
  1401.  
  1402.  
  1403. local Modem = {}
  1404. Modem.modems = {}
  1405.  
  1406.  
  1407. function Modem.exists()
  1408. Modem.exists = false
  1409. for _, side in pairs(rs.getSides()) do
  1410. if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  1411. Modem.exists = true
  1412.  
  1413. if not Modem.modems[side] then
  1414. Modem.modems[side] = peripheral.wrap(side)
  1415. end
  1416. end
  1417. end
  1418.  
  1419. return Modem.exists
  1420. end
  1421.  
  1422.  
  1423. function Modem.open(channel)
  1424. if not Modem.exists then
  1425. return false
  1426. end
  1427.  
  1428. for side, modem in pairs(Modem.modems) do
  1429. modem.open(channel)
  1430. rednet.open(side)
  1431. end
  1432.  
  1433. return true
  1434. end
  1435.  
  1436.  
  1437. function Modem.close(channel)
  1438. if not Modem.exists then
  1439. return false
  1440. end
  1441.  
  1442. for side, modem in pairs(Modem.modems) do
  1443. modem.close(channel)
  1444. end
  1445.  
  1446. return true
  1447. end
  1448.  
  1449.  
  1450. function Modem.closeAll()
  1451. if not Modem.exists then
  1452. return false
  1453. end
  1454.  
  1455. for side, modem in pairs(Modem.modems) do
  1456. modem.closeAll()
  1457. end
  1458.  
  1459. return true
  1460. end
  1461.  
  1462.  
  1463. function Modem.isOpen(channel)
  1464. if not Modem.exists then
  1465. return false
  1466. end
  1467.  
  1468. local isOpen = false
  1469. for side, modem in pairs(Modem.modems) do
  1470. if modem.isOpen(channel) then
  1471. isOpen = true
  1472. break
  1473. end
  1474. end
  1475.  
  1476. return isOpen
  1477. end
  1478.  
  1479.  
  1480. function Modem.transmit(channel, msg)
  1481. if not Modem.exists then
  1482. return false
  1483. end
  1484.  
  1485. if not Modem.isOpen(channel) then
  1486. Modem.open(channel)
  1487. end
  1488.  
  1489. for side, modem in pairs(Modem.modems) do
  1490. modem.transmit(channel, channel, msg)
  1491. end
  1492.  
  1493. return true
  1494. end
  1495.  
  1496.  
  1497.  
  1498. -- Handshake
  1499.  
  1500.  
  1501. local Handshake = {}
  1502.  
  1503. Handshake.prime = 625210769
  1504. Handshake.channel = 54569
  1505. Handshake.base = -1
  1506. Handshake.secret = -1
  1507. Handshake.sharedSecret = -1
  1508. Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]"
  1509. Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)"
  1510.  
  1511.  
  1512. function Handshake.exponentWithModulo(base, exponent, modulo)
  1513. local remainder = base
  1514.  
  1515. for i = 1, exponent-1 do
  1516. remainder = remainder * remainder
  1517. if remainder >= modulo then
  1518. remainder = remainder % modulo
  1519. end
  1520. end
  1521.  
  1522. return remainder
  1523. end
  1524.  
  1525.  
  1526. function Handshake.clear()
  1527. Handshake.base = -1
  1528. Handshake.secret = -1
  1529. Handshake.sharedSecret = -1
  1530. end
  1531.  
  1532.  
  1533. function Handshake.generateInitiatorData()
  1534. Handshake.base = math.random(10,99999)
  1535. Handshake.secret = math.random(10,99999)
  1536. return {
  1537. type = "initiate",
  1538. prime = Handshake.prime,
  1539. base = Handshake.base,
  1540. moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1541. }
  1542. end
  1543.  
  1544.  
  1545. function Handshake.generateResponseData(initiatorData)
  1546. local isPrimeANumber = type(initiatorData.prime) == "number"
  1547. local isPrimeMatching = initiatorData.prime == Handshake.prime
  1548. local isBaseANumber = type(initiatorData.base) == "number"
  1549. local isInitiator = initiatorData.type == "initiate"
  1550. local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number"
  1551. local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber
  1552.  
  1553. if areAllNumbersNumbers and isPrimeMatching then
  1554. if isInitiator then
  1555. Handshake.base = initiatorData.base
  1556. Handshake.secret = math.random(10,99999)
  1557. Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1558. return {
  1559. type = "response",
  1560. prime = Handshake.prime,
  1561. base = Handshake.base,
  1562. moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1563. }, Handshake.sharedSecret
  1564. elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then
  1565. Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1566. return Handshake.sharedSecret
  1567. else
  1568. return false
  1569. end
  1570. else
  1571. return false
  1572. end
  1573. end
  1574.  
  1575.  
  1576.  
  1577. -- Secure Connection
  1578.  
  1579.  
  1580. local SecureConnection = {}
  1581. SecureConnection.__index = SecureConnection
  1582.  
  1583.  
  1584. SecureConnection.packetHeaderA = "["..protocolName.."-"
  1585. SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]"
  1586. SecureConnection.packetMatchA = "%["..protocolName.."%-"
  1587. SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)"
  1588. SecureConnection.connectionTimeout = 0.1
  1589. SecureConnection.successPacketTimeout = 0.1
  1590.  
  1591.  
  1592. function SecureConnection.new(secret, key, identifier, distance, isRednet)
  1593. local self = setmetatable({}, SecureConnection)
  1594. self:setup(secret, key, identifier, distance, isRednet)
  1595. return self
  1596. end
  1597.  
  1598.  
  1599. function SecureConnection:setup(secret, key, identifier, distance, isRednet)
  1600. local rawSecret
  1601.  
  1602. if isRednet then
  1603. self.isRednet = true
  1604. self.distance = -1
  1605. self.rednet_id = distance
  1606. rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1607. "|" .. tostring(key) .. "|rednet"
  1608. else
  1609. self.isRednet = false
  1610. self.distance = distance
  1611. rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1612. "|" .. tostring(key) .. "|" .. tostring(distance)
  1613. end
  1614.  
  1615. self.identifier = identifier
  1616. self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB
  1617. self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB
  1618. self.secret = Cryptography.sha.sha256(rawSecret)
  1619. self.channel = Cryptography.channel(self.secret)
  1620.  
  1621. if not self.isRednet then
  1622. Modem.open(self.channel)
  1623. end
  1624. end
  1625.  
  1626.  
  1627. function SecureConnection:verifyHeader(msg)
  1628. if msg:match(self.packetMatch) then
  1629. return true
  1630. else
  1631. return false
  1632. end
  1633. end
  1634.  
  1635.  
  1636. function SecureConnection:sendMessage(msg, rednetProtocol)
  1637. print(self.packetHeader)
  1638. print(msg)
  1639. print(self.secret)
  1640. os.sleep(1000)
  1641. local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret)
  1642. local encryptedMsg = self.packetHeader .. rawEncryptedMsg
  1643.  
  1644. if self.isRednet then
  1645. rednet.send(self.rednet_id, encryptedMsg, rednetProtocol)
  1646. return true
  1647. else
  1648. return Modem.transmit(self.channel, encryptedMsg)
  1649. end
  1650. end
  1651.  
  1652.  
  1653. function SecureConnection:decryptMessage(msg)
  1654. if self:verifyHeader(msg) then
  1655. local encrypted = msg:match(self.packetMatch)
  1656.  
  1657. local unencryptedMsg = nil
  1658. pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end)
  1659. if not unencryptedMsg then
  1660. return false, "Could not decrypt"
  1661. end
  1662.  
  1663. if self:verifyHeader(unencryptedMsg) then
  1664. return true, unencryptedMsg:match(self.packetMatch)
  1665. else
  1666. return false, "Could not verify"
  1667. end
  1668. else
  1669. return false, "Could not stage 1 verify"
  1670. end
  1671. end
  1672.  
  1673.  
  1674.  
  1675. -- RDNT Protocol
  1676.  
  1677.  
  1678. protocols["rdnt"] = {}
  1679.  
  1680. local header = {}
  1681. header.dnsPacket = "[Firewolf-DNS-Packet]"
  1682. header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$"
  1683. header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]"
  1684. header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$"
  1685. header.responseMatchA = "^%[Firewolf%-"
  1686. header.responseMatchB = "%-"
  1687. header.responseMatchC = "%-Handshake%-Response%](.+)$"
  1688. header.requestHeaderA = "[Firewolf-"
  1689. header.requestHeaderB = "-Handshake-Request]"
  1690. header.pageRequestHeaderA = "[Firewolf-"
  1691. header.pageRequestHeaderB = "-Page-Request]"
  1692. header.pageResponseMatchA = "^%[Firewolf%-"
  1693. header.pageResponseMatchB = "%-Page%-Response%]%[HEADER%](.-)%[BODY%](.+)$"
  1694. header.closeHeaderA = "[Firewolf-"
  1695. header.closeHeaderB = "-Connection-Close]"
  1696.  
  1697.  
  1698. protocols["rdnt"]["setup"] = function()
  1699. if not Modem.exists() then
  1700. error("No modem found!")
  1701. end
  1702. end
  1703.  
  1704.  
  1705. protocols["rdnt"]["fetchAllSearchResults"] = function()
  1706. Modem.open(publicDNSChannel)
  1707. Modem.open(publicResponseChannel)
  1708. Modem.transmit(publicDNSChannel, header.dnsPacket)
  1709. Modem.close(publicDNSChannel)
  1710.  
  1711. rednet.broadcast(header.dnsPacket, header.rednetHeader .. publicDNSChannel)
  1712.  
  1713. local uniqueServers = {}
  1714. local uniqueDomains = {}
  1715.  
  1716. local timer = os.startTimer(searchResultTimeout)
  1717.  
  1718. while true do
  1719. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1720. if event == "modem_message" then
  1721. if channel == publicResponseChannel and message:match(header.dnsHeaderMatch) then
  1722. if not uniqueServers[tostring(dist)] then
  1723. uniqueServers[tostring(dist)] = true
  1724. local domain = message:match(header.dnsHeaderMatch)
  1725. if not uniqueDomains[domain] then
  1726. if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1727. timer = os.startTimer(searchResultTimeout)
  1728. uniqueDomains[message:match(header.dnsHeaderMatch)] = tostring(dist)
  1729. end
  1730. end
  1731. end
  1732. end
  1733. elseif event == "rednet_message" and allowUnencryptedConnections then
  1734. if protocol and tonumber(protocol:match(header.rednetMatch)) == publicResponseChannel and channel:match(header.dnsHeaderMatch) then
  1735. if not uniqueServers[tostring(id)] then
  1736. uniqueServers[tostring(id)] = true
  1737. local domain = channel:match(header.dnsHeaderMatch)
  1738. if not uniqueDomains[domain] then
  1739. if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1740. timer = os.startTimer(searchResultTimeout)
  1741. uniqueDomains[domain] = tostring(id)
  1742. end
  1743. end
  1744. end
  1745. end
  1746. elseif event == "timer" and id == timer then
  1747. local results = {}
  1748. for k, _ in pairs(uniqueDomains) do
  1749. table.insert(results, k)
  1750. end
  1751.  
  1752. return results
  1753. end
  1754. end
  1755. end
  1756.  
  1757.  
  1758. protocols["rdnt"]["fetchConnectionObject"] = function(url)
  1759. local serverChannel = Cryptography.channel(url)
  1760. local requestHeader = header.requestHeaderA .. url .. header.requestHeaderB
  1761. local responseMatch = header.responseMatchA .. Cryptography.sanatize(url) .. header.responseMatchB
  1762.  
  1763. local serializedHandshake = textutils.serialize(Handshake.generateInitiatorData())
  1764.  
  1765. local rednetResults = {}
  1766. local directResults = {}
  1767.  
  1768. local disconnectOthers = function(ignoreDirect)
  1769. for k,v in pairs(rednetResults) do
  1770. v.close()
  1771. end
  1772. for k,v in pairs(directResults) do
  1773. if k ~= ignoreDirect then
  1774. v.close()
  1775. end
  1776. end
  1777. end
  1778.  
  1779. local timer = os.startTimer(initiationTimeout)
  1780.  
  1781. Modem.open(serverChannel)
  1782. Modem.transmit(serverChannel, requestHeader .. serializedHandshake)
  1783.  
  1784. rednet.broadcast(requestHeader .. serializedHandshake, header.rednetHeader .. serverChannel)
  1785.  
  1786. -- Extendable to have server selection
  1787.  
  1788. while true do
  1789. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1790. if event == "modem_message" then
  1791. local fullMatch = responseMatch .. tostring(dist) .. header.responseMatchC
  1792. if channel == serverChannel and message:match(fullMatch) and type(textutils.unserialize(message:match(fullMatch))) == "table" then
  1793. local key = Handshake.generateResponseData(textutils.unserialize(message:match(fullMatch)))
  1794. if key then
  1795. local connection = SecureConnection.new(key, url, url, dist)
  1796. table.insert(directResults, {
  1797. connection = connection,
  1798. fetchPage = function(page)
  1799. if not connection then
  1800. return nil
  1801. end
  1802.  
  1803. local fetchTimer = os.startTimer(fetchTimeout)
  1804.  
  1805. local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1806. local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1807.  
  1808. connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1809.  
  1810. while true do
  1811. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1812. if event == "modem_message" and channel == connection.channel and connection:verifyHeader(message) then
  1813. local resp, data = connection:decryptMessage(message)
  1814. if not resp then
  1815. -- Decryption error
  1816. elseif data and data ~= page then
  1817. if data:match(pageResponseMatch) then
  1818. local head, body = data:match(pageResponseMatch)
  1819. return body, textutils.unserialize(head)
  1820. end
  1821. end
  1822. elseif event == "timer" and id == fetchTimer then
  1823. return nil
  1824. end
  1825. end
  1826. end,
  1827. close = function()
  1828. if connection ~= nil then
  1829. connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1830. Modem.close(connection.channel)
  1831. connection = nil
  1832. end
  1833. end
  1834. })
  1835.  
  1836. disconnectOthers(1)
  1837. return directResults[1]
  1838. end
  1839. end
  1840. elseif event == "rednet_message" then
  1841. local fullMatch = responseMatch .. os.getComputerID() .. header.responseMatchC
  1842. if protocol and tonumber(protocol:match(header.rednetMatch)) == serverChannel and channel:match(fullMatch) and type(textutils.unserialize(channel:match(fullMatch))) == "table" then
  1843. local key = Handshake.generateResponseData(textutils.unserialize(channel:match(fullMatch)))
  1844. if key then
  1845. local connection = SecureConnection.new(key, url, url, id, true)
  1846. table.insert(rednetResults, {
  1847. connection = connection,
  1848. fetchPage = function(page)
  1849. if not connection then
  1850. return nil
  1851. end
  1852.  
  1853. local fetchTimer = os.startTimer(fetchTimeout)
  1854.  
  1855. local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1856. local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1857.  
  1858. connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1859.  
  1860. while true do
  1861. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1862. if event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == connection.channel and connection:verifyHeader(channel) then
  1863. local resp, data = connection:decryptMessage(channel)
  1864. if not resp then
  1865. -- Decryption error
  1866. elseif data and data ~= page then
  1867. if data:match(pageResponseMatch) then
  1868. local head, body = data:match(pageResponseMatch)
  1869. return body, textutils.unserialize(head)
  1870. end
  1871. end
  1872. elseif event == "timer" and id == fetchTimer then
  1873. return nil
  1874. end
  1875. end
  1876. end,
  1877. close = function()
  1878. connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1879. Modem.close(connection.channel)
  1880. connection = nil
  1881. end
  1882. })
  1883.  
  1884. if #rednetResults == 1 then
  1885. timer = os.startTimer(0.2)
  1886. end
  1887. end
  1888. end
  1889. elseif event == "timer" and id == timer then
  1890. -- Return
  1891. if #directResults > 0 then
  1892. disconnectOthers(1)
  1893. return directResults[1]
  1894. elseif #rednetResults > 0 then
  1895. local lowestID = math.huge
  1896. local lowestResult = nil
  1897. for k,v in pairs(rednetResults) do
  1898. if v.connection.rednet_id < lowestID then
  1899. lowestID = v.connection.rednet_id
  1900. lowestResult = v
  1901. end
  1902. end
  1903.  
  1904. for k,v in pairs(rednetResults) do
  1905. if v.connection.rednet_id ~= lowestID then
  1906. v.close()
  1907. end
  1908. end
  1909.  
  1910. return lowestResult
  1911. else
  1912. return nil
  1913. end
  1914. end
  1915. end
  1916. end
  1917.  
  1918.  
  1919.  
  1920. -- Fetching Raw Data
  1921.  
  1922.  
  1923. local fetchSearchResultsForQuery = function(query)
  1924. local all = protocols[currentProtocol]["fetchAllSearchResults"]()
  1925. local results = {}
  1926. if query and query:len() > 0 then
  1927. for _, v in pairs(all) do
  1928. if v:find(query:lower()) then
  1929. table.insert(results, v)
  1930. end
  1931. end
  1932. else
  1933. results = all
  1934. end
  1935.  
  1936. table.sort(results)
  1937. return results
  1938. end
  1939.  
  1940.  
  1941. local getConnectionObjectFromURL = function(url)
  1942. local domain = url:match("^([^/]+)")
  1943. return protocols[currentProtocol]["fetchConnectionObject"](domain)
  1944. end
  1945.  
  1946.  
  1947. local determineLanguage = function(header)
  1948. if type(header) == "table" then
  1949. if header.language and header.language == "Firewolf Markup" then
  1950. return "fwml"
  1951. else
  1952. return "lua"
  1953. end
  1954. else
  1955. return "lua"
  1956. end
  1957. end
  1958.  
  1959.  
  1960.  
  1961. -- History
  1962.  
  1963.  
  1964. local appendToHistory = function(url)
  1965. if history[1] ~= url then
  1966. table.insert(history, 1, url)
  1967. end
  1968. end
  1969.  
  1970.  
  1971.  
  1972. -- Fetch Websites
  1973.  
  1974.  
  1975. local loadingAnimation = function()
  1976. local state = -2
  1977.  
  1978. term.setTextColor(theme.text)
  1979. term.setBackgroundColor(theme.accent)
  1980.  
  1981. term.setCursorPos(w - 5, 1)
  1982. term.write("[= ]")
  1983.  
  1984. local timer = os.startTimer(animationInterval)
  1985.  
  1986. while true do
  1987. local event, timerID = os.pullEvent()
  1988. if event == "timer" and timerID == timer then
  1989. term.setTextColor(theme.text)
  1990. term.setBackgroundColor(theme.accent)
  1991.  
  1992. state = state + 1
  1993.  
  1994. term.setCursorPos(w - 5, 1)
  1995. term.write("[ ]")
  1996. term.setCursorPos(w - 2 - math.abs(state), 1)
  1997. term.write("=")
  1998.  
  1999. if state == 2 then
  2000. state = -2
  2001. end
  2002.  
  2003. timer = os.startTimer(animationInterval)
  2004. end
  2005. end
  2006. end
  2007.  
  2008.  
  2009. local normalizeURL = function(url)
  2010. url = url:lower():gsub(" ", "")
  2011. if url == "home" or url == "homepage" then
  2012. url = "firewolf"
  2013. end
  2014.  
  2015. return url
  2016. end
  2017.  
  2018.  
  2019. local normalizePage = function(page)
  2020. if not page then page = "" end
  2021. page = page:lower()
  2022. if page == "" then
  2023. page = "/"
  2024. end
  2025. return page
  2026. end
  2027.  
  2028.  
  2029. local determineActionForURL = function(url)
  2030. if url:len() > 0 and url:gsub("/", ""):len() == 0 then
  2031. return "none"
  2032. end
  2033.  
  2034. if url == "exit" then
  2035. return "exit"
  2036. elseif builtInSites["display"][url] then
  2037. return "internal website"
  2038. elseif url == "" then
  2039. local results = fetchSearchResultsForQuery()
  2040. if #results > 0 then
  2041. return "search", results
  2042. else
  2043. return "none"
  2044. end
  2045. else
  2046. local connection = getConnectionObjectFromURL(url)
  2047. if connection then
  2048. return "external website", connection
  2049. else
  2050. local results = fetchSearchResultsForQuery(url)
  2051. if #results > 0 then
  2052. return "search", results
  2053. else
  2054. return "none"
  2055. end
  2056. end
  2057. end
  2058. end
  2059.  
  2060.  
  2061. local fetchSearch = function(url, results)
  2062. return languages["lua"]["runWithoutAntivirus"](builtInSites["search"], results)
  2063. end
  2064.  
  2065.  
  2066. local fetchInternal = function(url)
  2067. return languages["lua"]["runWithoutAntivirus"](builtInSites["display"][url])
  2068. end
  2069.  
  2070.  
  2071. local fetchError = function(err)
  2072. return languages["lua"]["runWithoutAntivirus"](builtInSites["error"], err)
  2073. end
  2074.  
  2075.  
  2076. local fetchExternal = function(url, connection)
  2077. if connection.multipleServers then
  2078. -- Please forgive me
  2079. -- GravityScore forced me to do it like this
  2080. -- I don't mean it, I really don't.
  2081. connection = connection.servers[1]
  2082. end
  2083.  
  2084. local page = normalizePage(url:match("^[^/]+/(.+)"))
  2085. local contents, head = connection.fetchPage(page)
  2086. if contents then
  2087. if type(contents) ~= "string" then
  2088. return fetchNone()
  2089. else
  2090. local language = determineLanguage(head)
  2091. return languages[language]["run"](contents, page, connection)
  2092. end
  2093. else
  2094. if connection then
  2095. connection.close()
  2096. return "retry"
  2097. end
  2098. return fetchError("A connection error/timeout has occurred!")
  2099. end
  2100. end
  2101.  
  2102.  
  2103. local fetchNone = function()
  2104. return languages["lua"]["runWithoutAntivirus"](builtInSites["noresults"])
  2105. end
  2106.  
  2107.  
  2108. local fetchURL = function(url, inheritConnection)
  2109. url = normalizeURL(url)
  2110. currentWebsiteURL = url
  2111.  
  2112. if inheritConnection then
  2113. local resp = fetchExternal(url, inheritConnection)
  2114. if resp ~= "retry" then
  2115. return resp, false, inheritConnection
  2116. end
  2117. end
  2118.  
  2119. local action, connection = determineActionForURL(url)
  2120.  
  2121. if action == "search" then
  2122. return fetchSearch(url, connection), true
  2123. elseif action == "internal website" then
  2124. return fetchInternal(url), true
  2125. elseif action == "external website" then
  2126. local resp = fetchExternal(url, connection)
  2127. if resp == "retry" then
  2128. return fetchError("A connection error/timeout has occurred!"), false, connection
  2129. else
  2130. return resp, false, connection
  2131. end
  2132. elseif action == "none" then
  2133. return fetchNone(), true
  2134. elseif action == "exit" then
  2135. os.queueEvent("terminate")
  2136. end
  2137.  
  2138. return nil
  2139. end
  2140.  
  2141.  
  2142.  
  2143. -- Tabs
  2144.  
  2145.  
  2146. local switchTab = function(index, shouldntResume)
  2147. if not tabs[index] then
  2148. return
  2149. end
  2150.  
  2151. if tabs[currentTab].win then
  2152. tabs[currentTab].win.setVisible(false)
  2153. end
  2154.  
  2155. currentTab = index
  2156. isMenubarOpen = tabs[currentTab].isMenubarOpen
  2157. currentWebsiteURL = tabs[currentTab].url
  2158.  
  2159. term.redirect(originalTerminal)
  2160. clear(theme.background, theme.text)
  2161. drawMenubar()
  2162.  
  2163. term.redirect(tabs[currentTab].win)
  2164. term.setCursorPos(1, 1)
  2165. tabs[currentTab].win.setVisible(true)
  2166. tabs[currentTab].win.redraw()
  2167.  
  2168. if not shouldntResume then
  2169. coroutine.resume(tabs[currentTab].thread)
  2170. end
  2171. end
  2172.  
  2173.  
  2174. local closeCurrentTab = function()
  2175. if #tabs <= 0 then
  2176. return
  2177. end
  2178.  
  2179. table.remove(tabs, currentTab)
  2180.  
  2181. currentTab = math.max(currentTab - 1, 1)
  2182. switchTab(currentTab, true)
  2183. end
  2184.  
  2185.  
  2186. local loadTab = function(index, url, givenFunc)
  2187. url = normalizeURL(url)
  2188.  
  2189. local func = nil
  2190. local isOpen = true
  2191. local currentConnection = false
  2192.  
  2193. isMenubarOpen = true
  2194. currentWebsiteURL = url
  2195. drawMenubar()
  2196.  
  2197. if tabs[index] and tabs[index].connection and tabs[index].url then
  2198. if url:match("^([^/]+)") == tabs[index].url:match("^([^/]+)") then
  2199. currentConnection = tabs[index].connection
  2200. else
  2201. tabs[index].connection.close()
  2202. tabs[index].connection = nil
  2203. end
  2204. end
  2205.  
  2206. if givenFunc then
  2207. func = givenFunc
  2208. else
  2209. parallel.waitForAny(function()
  2210. func, isOpen, connection = fetchURL(url, currentConnection)
  2211. end, function()
  2212. while true do
  2213. local event, key = os.pullEvent()
  2214. if event == "key" and (key == 29 or key == 157) then
  2215. break
  2216. end
  2217. end
  2218. end, loadingAnimation)
  2219. end
  2220.  
  2221. if func then
  2222. appendToHistory(url)
  2223.  
  2224. tabs[index] = {}
  2225. tabs[index].url = url
  2226. tabs[index].connection = connection
  2227. tabs[index].win = window.create(originalTerminal, 1, 1, w, h, false)
  2228.  
  2229. tabs[index].thread = coroutine.create(func)
  2230. tabs[index].isMenubarOpen = isOpen
  2231. tabs[index].isMenubarPermanent = isOpen
  2232.  
  2233. tabs[index].ox = 1
  2234. tabs[index].oy = 1
  2235.  
  2236. term.redirect(tabs[index].win)
  2237. clear(theme.background, theme.text)
  2238.  
  2239. switchTab(index)
  2240. end
  2241. end
  2242.  
  2243.  
  2244.  
  2245. -- Website Environments
  2246.  
  2247.  
  2248. local getWhitelistedEnvironment = function()
  2249. local env = {}
  2250.  
  2251. local function copy(source, destination, key)
  2252. destination[key] = {}
  2253. for k, v in pairs(source) do
  2254. destination[key][k] = v
  2255. end
  2256. end
  2257.  
  2258. copy(bit, env, "bit")
  2259. copy(colors, env, "colors")
  2260. copy(colours, env, "colours")
  2261. copy(coroutine, env, "coroutine")
  2262.  
  2263. copy(disk, env, "disk")
  2264. env["disk"]["setLabel"] = nil
  2265. env["disk"]["eject"] = nil
  2266.  
  2267. copy(gps, env, "gps")
  2268. copy(help, env, "help")
  2269. copy(keys, env, "keys")
  2270. copy(math, env, "math")
  2271.  
  2272. copy(os, env, "os")
  2273. env["os"]["run"] = nil
  2274. env["os"]["shutdown"] = nil
  2275. env["os"]["reboot"] = nil
  2276. env["os"]["setComputerLabel"] = nil
  2277. env["os"]["queueEvent"] = nil
  2278. env["os"]["pullEvent"] = function(filter)
  2279. while true do
  2280. local event = {os.pullEvent(filter)}
  2281. if not filter then
  2282. return unpack(event)
  2283. elseif filter and event[1] == filter then
  2284. return unpack(event)
  2285. end
  2286. end
  2287. end
  2288. env["os"]["pullEventRaw"] = env["os"]["pullEvent"]
  2289.  
  2290. copy(paintutils, env, "paintutils")
  2291. copy(parallel, env, "parallel")
  2292. copy(peripheral, env, "peripheral")
  2293. copy(rednet, env, "rednet")
  2294. copy(redstone, env, "redstone")
  2295. copy(redstone, env, "rs")
  2296.  
  2297. copy(shell, env, "shell")
  2298. env["shell"]["run"] = nil
  2299. env["shell"]["exit"] = nil
  2300. env["shell"]["setDir"] = nil
  2301. env["shell"]["setAlias"] = nil
  2302. env["shell"]["clearAlias"] = nil
  2303. env["shell"]["setPath"] = nil
  2304.  
  2305. copy(string, env, "string")
  2306. copy(table, env, "table")
  2307.  
  2308. copy(term, env, "term")
  2309. env["term"]["redirect"] = nil
  2310. env["term"]["restore"] = nil
  2311.  
  2312. copy(textutils, env, "textutils")
  2313. copy(vector, env, "vector")
  2314.  
  2315. if turtle then
  2316. copy(turtle, env, "turtle")
  2317. end
  2318.  
  2319. if http then
  2320. copy(http, env, "http")
  2321. end
  2322.  
  2323. env["assert"] = assert
  2324. env["printError"] = printError
  2325. env["tonumber"] = tonumber
  2326. env["tostring"] = tostring
  2327. env["type"] = type
  2328. env["next"] = next
  2329. env["unpack"] = unpack
  2330. env["pcall"] = pcall
  2331. env["xpcall"] = xpcall
  2332. env["sleep"] = sleep
  2333. env["pairs"] = pairs
  2334. env["ipairs"] = ipairs
  2335. env["read"] = read
  2336. env["write"] = write
  2337. env["select"] = select
  2338. env["print"] = print
  2339. env["setmetatable"] = setmetatable
  2340. env["getmetatable"] = getmetatable
  2341.  
  2342. env["_G"] = env
  2343.  
  2344. return env
  2345. end
  2346.  
  2347.  
  2348. local overrideEnvironment = function(env)
  2349. local localTerm = {}
  2350. for k, v in pairs(term) do
  2351. localTerm[k] = v
  2352. end
  2353.  
  2354. env["term"]["clear"] = function()
  2355. localTerm.clear()
  2356. drawMenubar()
  2357. end
  2358.  
  2359. env["term"]["scroll"] = function(n)
  2360. localTerm.scroll(n)
  2361. drawMenubar()
  2362. end
  2363.  
  2364. env["shell"]["getRunningProgram"] = function()
  2365. return currentWebsiteURL
  2366. end
  2367. end
  2368.  
  2369. local urlEncode = function(url)
  2370. local result = url
  2371.  
  2372. result = result:gsub("%%", "%%a")
  2373. result = result:gsub(":", "%%c")
  2374. result = result:gsub("/", "%%s")
  2375. result = result:gsub("\n", "%%n")
  2376. result = result:gsub(" ", "%%w")
  2377. result = result:gsub("&", "%%m")
  2378. result = result:gsub("%?", "%%q")
  2379. result = result:gsub("=", "%%e")
  2380. result = result:gsub("%.", "%%d")
  2381.  
  2382. return result
  2383. end
  2384.  
  2385. local urlDecode = function(url)
  2386. local result = url
  2387.  
  2388. result = result:gsub("%%c", ":")
  2389. result = result:gsub("%%s", "/")
  2390. result = result:gsub("%%n", "\n")
  2391. result = result:gsub("%%w", " ")
  2392. result = result:gsub("%%&", "&")
  2393. result = result:gsub("%%q", "%?")
  2394. result = result:gsub("%%e", "=")
  2395. result = result:gsub("%%d", "%.")
  2396. result = result:gsub("%%m", "%%")
  2397.  
  2398. return result
  2399. end
  2400.  
  2401. local applyAPIFunctions = function(env, connection)
  2402. env["firewolf"] = {}
  2403. env["firewolf"]["version"] = version
  2404. env["firewolf"]["domain"] = currentWebsiteURL:match("^[^/]+")
  2405.  
  2406. env["firewolf"]["redirect"] = function(url)
  2407. if type(url) ~= "string" then
  2408. return error("string (url) expected, got " .. type(url))
  2409. end
  2410.  
  2411. os.queueEvent(redirectEvent, url)
  2412. coroutine.yield()
  2413. end
  2414.  
  2415. env["firewolf"]["download"] = function(page)
  2416. if type(page) ~= "string" then
  2417. return error("string (page) expected")
  2418. end
  2419. local bannedNames = {"ls", "dir", "delete", "copy", "move", "list", "rm", "cp", "mv", "clear", "cd", "lua"}
  2420.  
  2421. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2422. if startSearch == 1 then
  2423. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2424. page = page:sub(endSearch + 2, -1)
  2425. else
  2426. page = page:sub(endSearch + 1, -1)
  2427. end
  2428. end
  2429.  
  2430. local filename = page:match("([^/]+)$")
  2431. if not filename then
  2432. return false, "Cannot download index"
  2433. end
  2434.  
  2435. for k, v in pairs(bannedNames) do
  2436. if filename == v then
  2437. return false, "Filename prohibited!"
  2438. end
  2439. end
  2440.  
  2441. if not fs.exists(downloadsLocation) then
  2442. fs.makeDir(downloadsLocation)
  2443. elseif not fs.isDir(downloadsLocation) then
  2444. return false, "Downloads disabled!"
  2445. end
  2446.  
  2447. contents = connection.fetchPage(normalizePage(page))
  2448. if type(contents) ~= "string" then
  2449. return false, "Download error!"
  2450. else
  2451. local f = io.open(downloadsLocation .. "/" .. filename, "w")
  2452. f:write(contents)
  2453. f:close()
  2454. return true, downloadsLocation .. "/" .. filename
  2455. end
  2456. end
  2457.  
  2458. env["firewolf"]["encode"] = function(vars)
  2459. if type(vars) ~= "table" then
  2460. return error("table (vars) expected, got " .. type(vars))
  2461. end
  2462.  
  2463. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2464. if startSearch == 1 then
  2465. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2466. page = page:sub(endSearch + 2, -1)
  2467. else
  2468. page = page:sub(endSearch + 1, -1)
  2469. end
  2470. end
  2471.  
  2472. local construct = "?"
  2473. for k,v in pairs(vars) do
  2474. construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2475. end
  2476. -- Get rid of that last ampersand
  2477. construct = construct:sub(1, -2)
  2478.  
  2479. return construct
  2480. end
  2481.  
  2482. env["firewolf"]["query"] = function(page, vars)
  2483. if type(page) ~= "string" then
  2484. return error("string (page) expected, got " .. type(page))
  2485. end
  2486. if vars and type(vars) ~= "table" then
  2487. return error("table (vars) expected, got " .. type(vars))
  2488. end
  2489.  
  2490. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2491. if startSearch == 1 then
  2492. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2493. page = page:sub(endSearch + 2, -1)
  2494. else
  2495. page = page:sub(endSearch + 1, -1)
  2496. end
  2497. end
  2498.  
  2499. local construct = page .. "?"
  2500. if vars then
  2501. for k,v in pairs(vars) do
  2502. construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2503. end
  2504. end
  2505. -- Get rid of that last ampersand
  2506. construct = construct:sub(1, -2)
  2507.  
  2508. contents = connection.fetchPage(normalizePage(construct))
  2509. if type(contents) == "string" then
  2510. return contents
  2511. else
  2512. return false
  2513. end
  2514. end
  2515.  
  2516. env["firewolf"]["loadImage"] = function(page)
  2517. if type(page) ~= "string" then
  2518. return error("string (page) expected, got " .. type(page))
  2519. end
  2520.  
  2521. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2522. if startSearch == 1 then
  2523. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2524. page = page:sub(endSearch + 2, -1)
  2525. else
  2526. page = page:sub(endSearch + 1, -1)
  2527. end
  2528. end
  2529.  
  2530. local filename = page:match("([^/]+)$")
  2531. if not filename then
  2532. return false, "Cannot load index as an image!"
  2533. end
  2534.  
  2535. contents = connection.fetchPage(normalizePage(page))
  2536. if type(contents) ~= "string" then
  2537. return false, "Download error!"
  2538. else
  2539. local colorLookup = {}
  2540. for n = 1, 16 do
  2541. colorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
  2542. end
  2543.  
  2544. local image = {}
  2545. for line in contents:gmatch("[^\n]+") do
  2546. local lines = {}
  2547. for x = 1, line:len() do
  2548. lines[x] = colorLookup[string.byte(line, x, x)] or 0
  2549. end
  2550. table.insert(image, lines)
  2551. end
  2552.  
  2553. return image
  2554. end
  2555. end
  2556.  
  2557. env["center"] = center
  2558. env["fill"] = fill
  2559. end
  2560.  
  2561.  
  2562. local getWebsiteEnvironment = function(antivirus, connection)
  2563. local env = {}
  2564.  
  2565. if antivirus then
  2566. env = getWhitelistedEnvironment()
  2567. overrideEnvironment(env)
  2568. else
  2569. setmetatable(env, {__index = _G})
  2570. end
  2571.  
  2572. applyAPIFunctions(env, connection)
  2573.  
  2574. return env
  2575. end
  2576.  
  2577.  
  2578.  
  2579. -- FWML Execution
  2580.  
  2581.  
  2582. local render = {}
  2583.  
  2584. render["functions"] = {}
  2585. render["functions"]["public"] = {}
  2586. render["alignations"] = {}
  2587.  
  2588. render["variables"] = {
  2589. scroll,
  2590. maxScroll,
  2591. align,
  2592. linkData = {},
  2593. blockLength,
  2594. link,
  2595. linkStart,
  2596. markers,
  2597. currentOffset,
  2598. }
  2599.  
  2600.  
  2601. local function getLine(loc, data)
  2602. local _, changes = data:sub(1, loc):gsub("\n", "")
  2603. if not changes then
  2604. return 1
  2605. else
  2606. return changes + 1
  2607. end
  2608. end
  2609.  
  2610.  
  2611. local function parseData(data)
  2612. local commands = {}
  2613. local searchPos = 1
  2614.  
  2615. while #data > 0 do
  2616. local sCmd, eCmd = data:find("%[[^%]]+%]", searchPos)
  2617. if sCmd then
  2618. sCmd = sCmd + 1
  2619. eCmd = eCmd - 1
  2620.  
  2621. if (sCmd > 2) then
  2622. if data:sub(sCmd - 2, sCmd - 2) == "\\" then
  2623. local t = data:sub(searchPos, sCmd - 1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2624. if #t > 0 then
  2625. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2626. commands[#commands][1] = commands[#commands][1] .. t
  2627. else
  2628. table.insert(commands, {t})
  2629. end
  2630. end
  2631. searchPos = sCmd
  2632. else
  2633. local t = data:sub(searchPos, sCmd - 2):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2634. if #t > 0 then
  2635. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2636. commands[#commands][1] = commands[#commands][1] .. t
  2637. else
  2638. table.insert(commands, {t})
  2639. end
  2640. end
  2641.  
  2642. t = data:sub(sCmd, eCmd):gsub("\n", "")
  2643. table.insert(commands, {getLine(sCmd, data), t})
  2644. searchPos = eCmd + 2
  2645. end
  2646. else
  2647. local t = data:sub(sCmd, eCmd):gsub("\n", "")
  2648. table.insert(commands, {getLine(sCmd, data), t})
  2649. searchPos = eCmd + 2
  2650. end
  2651. else
  2652. local t = data:sub(searchPos, -1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2653. if #t > 0 then
  2654. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2655. commands[#commands][1] = commands[#commands][1] .. t
  2656. else
  2657. table.insert(commands, {t})
  2658. end
  2659. end
  2660.  
  2661. break
  2662. end
  2663. end
  2664.  
  2665. return commands
  2666. end
  2667.  
  2668.  
  2669. local function proccessData(commands)
  2670. searchIndex = 0
  2671.  
  2672. while searchIndex < #commands do
  2673. searchIndex = searchIndex + 1
  2674.  
  2675. local length = 0
  2676. local origin = searchIndex
  2677.  
  2678. if type(commands[searchIndex][1]) == "string" then
  2679. length = length + #commands[searchIndex][1]
  2680. local endIndex = origin
  2681. for i = origin + 1, #commands do
  2682. if commands[i][2] then
  2683. local command = commands[i][2]:match("^(%w+)%s-")
  2684. if not (command == "c" or command == "color" or command == "bg"
  2685. or command == "background" or command == "newlink" or command == "endlink") then
  2686. endIndex = i
  2687. break
  2688. end
  2689. elseif commands[i][2] then
  2690.  
  2691. else
  2692. length = length + #commands[i][1]
  2693. end
  2694. if i == #commands then
  2695. endIndex = i
  2696. end
  2697. end
  2698.  
  2699. commands[origin][2] = length
  2700. searchIndex = endIndex
  2701. length = 0
  2702. end
  2703. end
  2704.  
  2705. return commands
  2706. end
  2707.  
  2708.  
  2709. local function parse(original)
  2710. return proccessData(parseData(original))
  2711. end
  2712.  
  2713.  
  2714. render["functions"]["display"] = function(text, length, offset, center)
  2715. if not offset then
  2716. offset = 0
  2717. end
  2718.  
  2719. return render.variables.align(text, length, w, offset, center);
  2720. end
  2721.  
  2722.  
  2723. render["functions"]["displayText"] = function(source)
  2724. if source[2] then
  2725. render.variables.blockLength = source[2]
  2726. if render.variables.link and not render.variables.linkStart then
  2727. render.variables.linkStart = render.functions.display(
  2728. source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2729. else
  2730. render.functions.display(source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2731. end
  2732. else
  2733. if render.variables.link and not render.variables.linkStart then
  2734. render.variables.linkStart = render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2735. else
  2736. render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2737. end
  2738. end
  2739. end
  2740.  
  2741.  
  2742. render["functions"]["public"]["br"] = function(source)
  2743. if render.variables.link then
  2744. return "Cannot insert new line within a link on line " .. source[1]
  2745. end
  2746.  
  2747. render.variables.scroll = render.variables.scroll + 1
  2748. render.variables.maxScroll = math.max(render.variables.scroll, render.variables.maxScroll)
  2749. end
  2750.  
  2751.  
  2752. render["functions"]["public"]["c "] = function(source)
  2753. local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2754. if colors[sColor] then
  2755. term.setTextColor(colors[sColor])
  2756. else
  2757. return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2758. end
  2759. end
  2760.  
  2761.  
  2762. render["functions"]["public"]["color "] = render["functions"]["public"]["c "]
  2763.  
  2764.  
  2765. render["functions"]["public"]["bg "] = function(source)
  2766. local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2767. if colors[sColor] then
  2768. term.setBackgroundColor(colors[sColor])
  2769. else
  2770. return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2771. end
  2772. end
  2773.  
  2774.  
  2775. render["functions"]["public"]["background "] = render["functions"]["public"]["bg "]
  2776.  
  2777.  
  2778. render["functions"]["public"]["newlink "] = function(source)
  2779. if render.variables.link then
  2780. return "Cannot nest links on line " .. source[1]
  2781. end
  2782.  
  2783. render.variables.link = source[2]:match("^%w+%s+(.+)$") or ""
  2784. render.variables.linkStart = false
  2785. end
  2786.  
  2787.  
  2788. render["functions"]["public"]["endlink"] = function(source)
  2789. if not render.variables.link then
  2790. return "Cannot end a link without a link on line " .. source[1]
  2791. end
  2792.  
  2793. local linkEnd = term.getCursorPos()-1
  2794. table.insert(render.variables.linkData, {render.variables.linkStart,
  2795. linkEnd, render.variables.scroll, render.variables.link})
  2796. render.variables.link = false
  2797. render.variables.linkStart = false
  2798. end
  2799.  
  2800.  
  2801. render["functions"]["public"]["offset "] = function(source)
  2802. local offset = tonumber((source[2]:match("^%w+%s+(.+)$") or ""))
  2803. if offset then
  2804. render.variables.currentOffset = offset
  2805. else
  2806. return "Invalid offset value: \"" .. (source[2]:match("^%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2807. end
  2808. end
  2809.  
  2810.  
  2811. render["functions"]["public"]["marker "] = function(source)
  2812. render.variables.markers[(source[2]:match("^%w+%s+(.+)$") or "")] = render.variables.scroll
  2813. end
  2814.  
  2815.  
  2816. render["functions"]["public"]["goto "] = function(source)
  2817. local location = source[2]:match("%w+%s+(.+)$")
  2818. if render.variables.markers[location] then
  2819. render.variables.scroll = render.variables.markers[location]
  2820. else
  2821. return "No such location: \"" .. (source[2]:match("%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2822. end
  2823. end
  2824.  
  2825.  
  2826. render["functions"]["public"]["box "] = function(source)
  2827. local sColor, align, height, width, offset, url = source[2]:match("^box (%a+) (%a+) (%-?%d+) (%-?%d+) (%-?%d+) ?([^ ]*)")
  2828. if not sColor then
  2829. return "Invalid box syntax on line " .. source[1]
  2830. end
  2831.  
  2832. local x, y = term.getCursorPos()
  2833. local startX
  2834.  
  2835. if align == "center" or align == "centre" then
  2836. startX = math.ceil((w / 2) - width / 2) + offset
  2837. elseif align == "left" then
  2838. startX = 1 + offset
  2839. elseif align == "right" then
  2840. startX = (w - width + 1) + offset
  2841. else
  2842. return "Invalid align option for box on line " .. source[1]
  2843. end
  2844.  
  2845. if not colors[sColor] then
  2846. return "Invalid color: \"" .. sColor .. "\" for box on line " .. source[1]
  2847. end
  2848.  
  2849. term.setBackgroundColor(colors[sColor])
  2850. for i = 0, height - 1 do
  2851. term.setCursorPos(startX, render.variables.scroll + i)
  2852. term.write(string.rep(" ", width))
  2853. if url:len() > 3 then
  2854. table.insert(render.variables.linkData, {startX, startX + width - 1, render.variables.scroll + i, url})
  2855. end
  2856. end
  2857.  
  2858. render.variables.maxScroll = math.max(render.variables.scroll + height - 1, render.variables.maxScroll)
  2859. term.setCursorPos(x, y)
  2860. end
  2861.  
  2862.  
  2863. render["alignations"]["left"] = function(text, length, _, offset)
  2864. local x, y = term.getCursorPos()
  2865. if length then
  2866. term.setCursorPos(1 + offset, render.variables.scroll)
  2867. term.write(text)
  2868. return 1 + offset
  2869. else
  2870. term.setCursorPos(x, render.variables.scroll)
  2871. term.write(text)
  2872. return x
  2873. end
  2874. end
  2875.  
  2876.  
  2877. render["alignations"]["right"] = function(text, length, width, offset)
  2878. local x, y = term.getCursorPos()
  2879. if length then
  2880. term.setCursorPos((width - length + 1) + offset, render.variables.scroll)
  2881. term.write(text)
  2882. return (width - length + 1) + offset
  2883. else
  2884. term.setCursorPos(x, render.variables.scroll)
  2885. term.write(text)
  2886. return x
  2887. end
  2888. end
  2889.  
  2890.  
  2891. render["alignations"]["center"] = function(text, length, _, offset, center)
  2892. local x, y = term.getCursorPos()
  2893. if length then
  2894. term.setCursorPos(math.ceil(center - length / 2) + offset, render.variables.scroll)
  2895. term.write(text)
  2896. return math.ceil(center - length / 2) + offset
  2897. else
  2898. term.setCursorPos(x, render.variables.scroll)
  2899. term.write(text)
  2900. return x
  2901. end
  2902. end
  2903.  
  2904.  
  2905. render["render"] = function(data, startScroll)
  2906. if startScroll == nil then
  2907. render.variables.startScroll = 0
  2908. else
  2909. render.variables.startScroll = startScroll
  2910. end
  2911.  
  2912. render.variables.scroll = startScroll + 1
  2913. render.variables.maxScroll = render.variables.scroll
  2914.  
  2915. render.variables.linkData = {}
  2916.  
  2917. render.variables.align = render.alignations.left
  2918.  
  2919. render.variables.blockLength = 0
  2920. render.variables.link = false
  2921. render.variables.linkStart = false
  2922. render.variables.markers = {}
  2923. render.variables.currentOffset = 0
  2924.  
  2925. for k, v in pairs(data) do
  2926. if type(v[2]) ~= "string" then
  2927. render.functions.displayText(v)
  2928. elseif v[2] == "<" or v[2] == "left" then
  2929. render.variables.align = render.alignations.left
  2930. elseif v[2] == ">" or v[2] == "right" then
  2931. render.variables.align = render.alignations.right
  2932. elseif v[2] == "=" or v[2] == "center" then
  2933. render.variables.align = render.alignations.center
  2934. else
  2935. local existentFunction = false
  2936.  
  2937. for name, func in pairs(render.functions.public) do
  2938. if v[2]:find(name) == 1 then
  2939. existentFunction = true
  2940. local ret = func(v)
  2941. if ret then
  2942. return ret
  2943. end
  2944. end
  2945. end
  2946.  
  2947. if not existentFunction then
  2948. return "Non-existent tag: \"" .. v[2] .. "\" on line " .. v[1]
  2949. end
  2950. end
  2951. end
  2952.  
  2953. return render.variables.linkData, render.variables.maxScroll - render.variables.startScroll
  2954. end
  2955.  
  2956.  
  2957.  
  2958. -- Lua Execution
  2959.  
  2960.  
  2961. languages["lua"] = {}
  2962. languages["fwml"] = {}
  2963.  
  2964.  
  2965. languages["lua"]["runWithErrorCatching"] = function(func, ...)
  2966. local _, err = pcall(func, ...)
  2967. if err then
  2968. os.queueEvent(websiteErrorEvent, err)
  2969. end
  2970. end
  2971.  
  2972.  
  2973. languages["lua"]["runWithoutAntivirus"] = function(func, ...)
  2974. local args = {...}
  2975. local env = getWebsiteEnvironment(false)
  2976. setfenv(func, env)
  2977. return function()
  2978. languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2979. end
  2980. end
  2981.  
  2982.  
  2983. languages["lua"]["run"] = function(contents, page, connection, ...)
  2984. local func, err = loadstring("sleep(0) " .. contents, page)
  2985. if err then
  2986. return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], err)
  2987. else
  2988. local args = {...}
  2989. local env = getWebsiteEnvironment(true, connection)
  2990. setfenv(func, env)
  2991. return function()
  2992. languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2993. end
  2994. end
  2995. end
  2996.  
  2997.  
  2998. languages["fwml"]["run"] = function(contents, page, connection, ...)
  2999. local err, data = pcall(parse, contents)
  3000. if not err then
  3001. return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], data)
  3002. end
  3003.  
  3004. return function()
  3005. local currentScroll = 0
  3006. local err, links, pageHeight = pcall(render.render, data, currentScroll)
  3007. if type(links) == "string" or not err then
  3008. term.clear()
  3009. os.queueEvent(websiteErrorEvent, links)
  3010. else
  3011. while true do
  3012. local e, scroll, x, y = os.pullEvent()
  3013. if e == "mouse_click" then
  3014. for k, v in pairs(links) do
  3015. if x >= math.min(v[1], v[2]) and x <= math.max(v[1], v[2]) and y == v[3] then
  3016. os.queueEvent(redirectEvent, v[4])
  3017. coroutine.yield()
  3018. end
  3019. end
  3020. elseif e == "mouse_scroll" then
  3021. if currentScroll - scroll - h >= -pageHeight and currentScroll - scroll <= 0 then
  3022. currentScroll = currentScroll - scroll
  3023. clear(theme.background, theme.text)
  3024. links = render.render(data, currentScroll)
  3025. end
  3026. elseif e == "key" and scroll == keys.up or scroll == keys.down then
  3027. local scrollAmount
  3028.  
  3029. if scroll == keys.up then
  3030. scrollAmount = 1
  3031. elseif scroll == keys.down then
  3032. scrollAmount = -1
  3033. end
  3034.  
  3035. local scrollLessHeight = currentScroll + scrollAmount - h >= -pageHeight
  3036. local scrollZero = currentScroll + scrollAmount <= 0
  3037. if scrollLessHeight and scrollZero then
  3038. currentScroll = currentScroll + scrollAmount
  3039. clear(theme.background, theme.text)
  3040. links = render.render(data, currentScroll)
  3041. end
  3042. end
  3043. end
  3044. end
  3045. end
  3046. end
  3047.  
  3048.  
  3049.  
  3050. -- Query Bar
  3051.  
  3052.  
  3053. local readNewWebsiteURL = function()
  3054. local onEvent = function(text, event, key, x, y)
  3055. if event == "mouse_click" then
  3056. if y == 2 then
  3057. local index = determineClickedTab(x, y)
  3058. if index == "new" and #tabs < maxTabs then
  3059. loadTab(#tabs + 1, "firewolf")
  3060. elseif index == "close" then
  3061. closeCurrentTab()
  3062. elseif index then
  3063. switchTab(index)
  3064. end
  3065.  
  3066. return {["nullifyText"] = true, ["exit"] = true}
  3067. elseif y > 2 then
  3068. return {["nullifyText"] = true, ["exit"] = true}
  3069. end
  3070. elseif event == "key" then
  3071. if key == 29 or key == 157 then
  3072. return {["nullifyText"] = true, ["exit"] = true}
  3073. end
  3074. end
  3075. end
  3076.  
  3077. isMenubarOpen = true
  3078. drawMenubar()
  3079. term.setCursorPos(2, 1)
  3080. term.setTextColor(theme.text)
  3081. term.setBackgroundColor(theme.accent)
  3082. term.clearLine()
  3083. term.write(currentProtocol .. "://")
  3084.  
  3085. local website = modifiedRead({
  3086. ["onEvent"] = onEvent,
  3087. ["displayLength"] = w - 9,
  3088. ["history"] = history,
  3089. })
  3090.  
  3091. if not website then
  3092. if not tabs[currentTab].isMenubarPermanent then
  3093. isMenubarOpen = false
  3094. menubarWindow.setVisible(false)
  3095. else
  3096. isMenubarOpen = true
  3097. menubarWindow.setVisible(true)
  3098. end
  3099.  
  3100. term.redirect(tabs[currentTab].win)
  3101. tabs[currentTab].win.setVisible(true)
  3102. tabs[currentTab].win.redraw()
  3103.  
  3104. return
  3105. elseif website == "exit" then
  3106. error()
  3107. end
  3108.  
  3109. loadTab(currentTab, website)
  3110. end
  3111.  
  3112.  
  3113.  
  3114. -- Event Management
  3115.  
  3116.  
  3117. local handleKeyDown = function(event)
  3118. if event[2] == 29 or event[2] == 157 then
  3119. readNewWebsiteURL()
  3120. return true
  3121. end
  3122.  
  3123. return false
  3124. end
  3125.  
  3126.  
  3127. local handleMouseDown = function(event)
  3128. if isMenubarOpen then
  3129. if event[4] == 1 then
  3130. readNewWebsiteURL()
  3131. return true
  3132. elseif event[4] == 2 then
  3133. local index = determineClickedTab(event[3], event[4])
  3134. if index == "new" and #tabs < maxTabs then
  3135. loadTab(#tabs + 1, "firewolf")
  3136. elseif index == "close" then
  3137. closeCurrentTab()
  3138. elseif index then
  3139. switchTab(index)
  3140. end
  3141.  
  3142. return true
  3143. end
  3144. end
  3145.  
  3146. return false
  3147. end
  3148.  
  3149.  
  3150. local handleEvents = function()
  3151. loadTab(1, "firewolf")
  3152. currentTab = 1
  3153.  
  3154. while true do
  3155. drawMenubar()
  3156. local event = {os.pullEventRaw()}
  3157. drawMenubar()
  3158.  
  3159. local cancelEvent = false
  3160. if event[1] == "terminate" then
  3161. break
  3162. elseif event[1] == "key" then
  3163. cancelEvent = handleKeyDown(event)
  3164. elseif event[1] == "mouse_click" then
  3165. cancelEvent = handleMouseDown(event)
  3166. elseif event[1] == websiteErrorEvent then
  3167. cancelEvent = true
  3168.  
  3169. loadTab(currentTab, tabs[currentTab].url, function()
  3170. builtInSites["crash"](event[2])
  3171. end)
  3172. elseif event[1] == redirectEvent then
  3173. cancelEvent = true
  3174.  
  3175. if (event[2]:match("^rdnt://(.+)$")) then
  3176. event[2] = event[2]:match("^rdnt://(.+)$")
  3177. end
  3178.  
  3179. loadTab(currentTab, event[2])
  3180. end
  3181.  
  3182. if not cancelEvent then
  3183. term.redirect(tabs[currentTab].win)
  3184. term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy)
  3185.  
  3186. coroutine.resume(tabs[currentTab].thread, unpack(event))
  3187.  
  3188. local ox, oy = term.getCursorPos()
  3189. tabs[currentTab].ox = ox
  3190. tabs[currentTab].oy = oy
  3191. end
  3192. end
  3193. end
  3194.  
  3195.  
  3196.  
  3197. -- Main
  3198.  
  3199.  
  3200. local main = function()
  3201. currentProtocol = "rdnt"
  3202. currentTab = 1
  3203.  
  3204. if term.isColor() then
  3205. theme = colorTheme
  3206. enableTabBar = true
  3207. else
  3208. theme = grayscaleTheme
  3209. enableTabBar = false
  3210. end
  3211.  
  3212. setupMenubar()
  3213. protocols[currentProtocol]["setup"]()
  3214.  
  3215. clear(theme.background, theme.text)
  3216. handleEvents()
  3217. end
  3218.  
  3219.  
  3220. local handleError = function(err)
  3221. clear(theme.background, theme.text)
  3222.  
  3223. fill(1, 3, w, 3, theme.subtle)
  3224. term.setCursorPos(1, 4)
  3225. center("Firewolf has crashed!")
  3226.  
  3227. term.setBackgroundColor(theme.background)
  3228. term.setCursorPos(1, 8)
  3229. centerSplit(err, w - 4)
  3230. print("\n")
  3231. center("Please report this error to")
  3232. center("GravityScore or 1lann.")
  3233. print("")
  3234. center("Press any key to exit.")
  3235.  
  3236. os.pullEvent("key")
  3237. os.queueEvent("")
  3238. os.pullEvent()
  3239. end
  3240.  
  3241. local _, err = pcall(main)
  3242. term.redirect(originalTerminal)
  3243.  
  3244. Modem.closeAll()
  3245.  
  3246. if err and not err:lower():find("terminate") then
  3247. handleError(err)
  3248. end
  3249.  
  3250.  
  3251. clear(colors.black, colors.white)
  3252. center("Thanks for using Firewolf " .. version)
  3253. center("Made by GravityScore and 1lann")
  3254. print("")
Add Comment
Please, Sign In to add comment