Guest User

Untitled

a guest
May 6th, 2016
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 75.71 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. print("entry2")
  1351. os.sleep(0.2)
  1352. return base64Encode(crypt(msg, key))
  1353. end
  1354.  
  1355.  
  1356. function Cryptography.aes.decrypt(msg, key)
  1357. return crypt(base64Decode(msg), key)
  1358. end
  1359.  
  1360.  
  1361. function Cryptography.base64.encode(msg)
  1362. print("entry1")
  1363. os.sleep(0.2)
  1364. return base64Encode(Cryptography.bytesFromMessage(msg))
  1365. end
  1366.  
  1367.  
  1368. function Cryptography.base64.decode(msg)
  1369. return Cryptography.messageFromBytes(base64Decode(msg))
  1370. end
  1371.  
  1372.  
  1373. function Cryptography.channel(text)
  1374. local hashed = Cryptography.sha.sha256(text)
  1375.  
  1376. local total = 0
  1377.  
  1378. for i = 1, hashed:len() do
  1379. total = total + string.byte(hashed:sub(i, i))
  1380. end
  1381.  
  1382. return (total % 55530) + 10000
  1383. end
  1384.  
  1385.  
  1386. function Cryptography.sanatize(text)
  1387. local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"}
  1388.  
  1389. for _, char in pairs(sanatizeChars) do
  1390. text = text:gsub("%"..char, "%%%"..char)
  1391. end
  1392. return text
  1393. end
  1394.  
  1395.  
  1396.  
  1397. -- Modem
  1398.  
  1399.  
  1400. local Modem = {}
  1401. Modem.modems = {}
  1402.  
  1403.  
  1404. function Modem.exists()
  1405. Modem.exists = false
  1406. for _, side in pairs(rs.getSides()) do
  1407. if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  1408. Modem.exists = true
  1409.  
  1410. if not Modem.modems[side] then
  1411. Modem.modems[side] = peripheral.wrap(side)
  1412. end
  1413. end
  1414. end
  1415.  
  1416. return Modem.exists
  1417. end
  1418.  
  1419.  
  1420. function Modem.open(channel)
  1421. if not Modem.exists then
  1422. return false
  1423. end
  1424.  
  1425. for side, modem in pairs(Modem.modems) do
  1426. modem.open(channel)
  1427. rednet.open(side)
  1428. end
  1429.  
  1430. return true
  1431. end
  1432.  
  1433.  
  1434. function Modem.close(channel)
  1435. if not Modem.exists then
  1436. return false
  1437. end
  1438.  
  1439. for side, modem in pairs(Modem.modems) do
  1440. modem.close(channel)
  1441. end
  1442.  
  1443. return true
  1444. end
  1445.  
  1446.  
  1447. function Modem.closeAll()
  1448. if not Modem.exists then
  1449. return false
  1450. end
  1451.  
  1452. for side, modem in pairs(Modem.modems) do
  1453. modem.closeAll()
  1454. end
  1455.  
  1456. return true
  1457. end
  1458.  
  1459.  
  1460. function Modem.isOpen(channel)
  1461. if not Modem.exists then
  1462. return false
  1463. end
  1464.  
  1465. local isOpen = false
  1466. for side, modem in pairs(Modem.modems) do
  1467. if modem.isOpen(channel) then
  1468. isOpen = true
  1469. break
  1470. end
  1471. end
  1472.  
  1473. return isOpen
  1474. end
  1475.  
  1476.  
  1477. function Modem.transmit(channel, msg)
  1478. if not Modem.exists then
  1479. return false
  1480. end
  1481.  
  1482. if not Modem.isOpen(channel) then
  1483. Modem.open(channel)
  1484. end
  1485.  
  1486. for side, modem in pairs(Modem.modems) do
  1487. modem.transmit(channel, channel, msg)
  1488. end
  1489.  
  1490. return true
  1491. end
  1492.  
  1493.  
  1494.  
  1495. -- Handshake
  1496.  
  1497.  
  1498. local Handshake = {}
  1499.  
  1500. Handshake.prime = 625210769
  1501. Handshake.channel = 54569
  1502. Handshake.base = -1
  1503. Handshake.secret = -1
  1504. Handshake.sharedSecret = -1
  1505. Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]"
  1506. Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)"
  1507.  
  1508.  
  1509. function Handshake.exponentWithModulo(base, exponent, modulo)
  1510. local remainder = base
  1511.  
  1512. for i = 1, exponent-1 do
  1513. remainder = remainder * remainder
  1514. if remainder >= modulo then
  1515. remainder = remainder % modulo
  1516. end
  1517. end
  1518.  
  1519. return remainder
  1520. end
  1521.  
  1522.  
  1523. function Handshake.clear()
  1524. Handshake.base = -1
  1525. Handshake.secret = -1
  1526. Handshake.sharedSecret = -1
  1527. end
  1528.  
  1529.  
  1530. function Handshake.generateInitiatorData()
  1531. Handshake.base = math.random(10,99999)
  1532. Handshake.secret = math.random(10,99999)
  1533. return {
  1534. type = "initiate",
  1535. prime = Handshake.prime,
  1536. base = Handshake.base,
  1537. moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1538. }
  1539. end
  1540.  
  1541.  
  1542. function Handshake.generateResponseData(initiatorData)
  1543. local isPrimeANumber = type(initiatorData.prime) == "number"
  1544. local isPrimeMatching = initiatorData.prime == Handshake.prime
  1545. local isBaseANumber = type(initiatorData.base) == "number"
  1546. local isInitiator = initiatorData.type == "initiate"
  1547. local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number"
  1548. local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber
  1549.  
  1550. if areAllNumbersNumbers and isPrimeMatching then
  1551. if isInitiator then
  1552. Handshake.base = initiatorData.base
  1553. Handshake.secret = math.random(10,99999)
  1554. Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1555. return {
  1556. type = "response",
  1557. prime = Handshake.prime,
  1558. base = Handshake.base,
  1559. moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1560. }, Handshake.sharedSecret
  1561. elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then
  1562. Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1563. return Handshake.sharedSecret
  1564. else
  1565. return false
  1566. end
  1567. else
  1568. return false
  1569. end
  1570. end
  1571.  
  1572.  
  1573.  
  1574. -- Secure Connection
  1575.  
  1576.  
  1577. local SecureConnection = {}
  1578. SecureConnection.__index = SecureConnection
  1579.  
  1580.  
  1581. SecureConnection.packetHeaderA = "["..protocolName.."-"
  1582. SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]"
  1583. SecureConnection.packetMatchA = "%["..protocolName.."%-"
  1584. SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)"
  1585. SecureConnection.connectionTimeout = 0.1
  1586. SecureConnection.successPacketTimeout = 0.1
  1587.  
  1588.  
  1589. function SecureConnection.new(secret, key, identifier, distance, isRednet)
  1590. local self = setmetatable({}, SecureConnection)
  1591. self:setup(secret, key, identifier, distance, isRednet)
  1592. return self
  1593. end
  1594.  
  1595.  
  1596. function SecureConnection:setup(secret, key, identifier, distance, isRednet)
  1597. local rawSecret
  1598.  
  1599. if isRednet then
  1600. self.isRednet = true
  1601. self.distance = -1
  1602. self.rednet_id = distance
  1603. rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1604. "|" .. tostring(key) .. "|rednet"
  1605. else
  1606. self.isRednet = false
  1607. self.distance = distance
  1608. rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1609. "|" .. tostring(key) .. "|" .. tostring(distance)
  1610. end
  1611.  
  1612. self.identifier = identifier
  1613. self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB
  1614. self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB
  1615. self.secret = Cryptography.sha.sha256(rawSecret)
  1616. self.channel = Cryptography.channel(self.secret)
  1617.  
  1618. if not self.isRednet then
  1619. Modem.open(self.channel)
  1620. end
  1621. end
  1622.  
  1623.  
  1624. function SecureConnection:verifyHeader(msg)
  1625. if msg:match(self.packetMatch) then
  1626. return true
  1627. else
  1628. return false
  1629. end
  1630. end
  1631.  
  1632.  
  1633. function SecureConnection:sendMessage(msg, rednetProtocol)
  1634. local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret)
  1635. local encryptedMsg = self.packetHeader .. rawEncryptedMsg
  1636.  
  1637. if self.isRednet then
  1638. rednet.send(self.rednet_id, encryptedMsg, rednetProtocol)
  1639. return true
  1640. else
  1641. return Modem.transmit(self.channel, encryptedMsg)
  1642. end
  1643. end
  1644.  
  1645.  
  1646. function SecureConnection:decryptMessage(msg)
  1647. if self:verifyHeader(msg) then
  1648. local encrypted = msg:match(self.packetMatch)
  1649.  
  1650. local unencryptedMsg = nil
  1651. pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end)
  1652. if not unencryptedMsg then
  1653. return false, "Could not decrypt"
  1654. end
  1655.  
  1656. if self:verifyHeader(unencryptedMsg) then
  1657. return true, unencryptedMsg:match(self.packetMatch)
  1658. else
  1659. return false, "Could not verify"
  1660. end
  1661. else
  1662. return false, "Could not stage 1 verify"
  1663. end
  1664. end
  1665.  
  1666.  
  1667.  
  1668. -- RDNT Protocol
  1669.  
  1670.  
  1671. protocols["rdnt"] = {}
  1672.  
  1673. local header = {}
  1674. header.dnsPacket = "[Firewolf-DNS-Packet]"
  1675. header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$"
  1676. header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]"
  1677. header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$"
  1678. header.responseMatchA = "^%[Firewolf%-"
  1679. header.responseMatchB = "%-"
  1680. header.responseMatchC = "%-Handshake%-Response%](.+)$"
  1681. header.requestHeaderA = "[Firewolf-"
  1682. header.requestHeaderB = "-Handshake-Request]"
  1683. header.pageRequestHeaderA = "[Firewolf-"
  1684. header.pageRequestHeaderB = "-Page-Request]"
  1685. header.pageResponseMatchA = "^%[Firewolf%-"
  1686. header.pageResponseMatchB = "%-Page%-Response%]%[HEADER%](.-)%[BODY%](.+)$"
  1687. header.closeHeaderA = "[Firewolf-"
  1688. header.closeHeaderB = "-Connection-Close]"
  1689.  
  1690.  
  1691. protocols["rdnt"]["setup"] = function()
  1692. if not Modem.exists() then
  1693. error("No modem found!")
  1694. end
  1695. end
  1696.  
  1697.  
  1698. protocols["rdnt"]["fetchAllSearchResults"] = function()
  1699. Modem.open(publicDNSChannel)
  1700. Modem.open(publicResponseChannel)
  1701. Modem.transmit(publicDNSChannel, header.dnsPacket)
  1702. Modem.close(publicDNSChannel)
  1703.  
  1704. rednet.broadcast(header.dnsPacket, header.rednetHeader .. publicDNSChannel)
  1705.  
  1706. local uniqueServers = {}
  1707. local uniqueDomains = {}
  1708.  
  1709. local timer = os.startTimer(searchResultTimeout)
  1710.  
  1711. while true do
  1712. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1713. if event == "modem_message" then
  1714. if channel == publicResponseChannel and message:match(header.dnsHeaderMatch) then
  1715. if not uniqueServers[tostring(dist)] then
  1716. uniqueServers[tostring(dist)] = true
  1717. local domain = message:match(header.dnsHeaderMatch)
  1718. if not uniqueDomains[domain] then
  1719. if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1720. timer = os.startTimer(searchResultTimeout)
  1721. uniqueDomains[message:match(header.dnsHeaderMatch)] = tostring(dist)
  1722. end
  1723. end
  1724. end
  1725. end
  1726. elseif event == "rednet_message" and allowUnencryptedConnections then
  1727. if protocol and tonumber(protocol:match(header.rednetMatch)) == publicResponseChannel and channel:match(header.dnsHeaderMatch) then
  1728. if not uniqueServers[tostring(id)] then
  1729. uniqueServers[tostring(id)] = true
  1730. local domain = channel:match(header.dnsHeaderMatch)
  1731. if not uniqueDomains[domain] then
  1732. if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1733. timer = os.startTimer(searchResultTimeout)
  1734. uniqueDomains[domain] = tostring(id)
  1735. end
  1736. end
  1737. end
  1738. end
  1739. elseif event == "timer" and id == timer then
  1740. local results = {}
  1741. for k, _ in pairs(uniqueDomains) do
  1742. table.insert(results, k)
  1743. end
  1744.  
  1745. return results
  1746. end
  1747. end
  1748. end
  1749.  
  1750.  
  1751. protocols["rdnt"]["fetchConnectionObject"] = function(url)
  1752. local serverChannel = Cryptography.channel(url)
  1753. local requestHeader = header.requestHeaderA .. url .. header.requestHeaderB
  1754. local responseMatch = header.responseMatchA .. Cryptography.sanatize(url) .. header.responseMatchB
  1755.  
  1756. local serializedHandshake = textutils.serialize(Handshake.generateInitiatorData())
  1757.  
  1758. local rednetResults = {}
  1759. local directResults = {}
  1760.  
  1761. local disconnectOthers = function(ignoreDirect)
  1762. for k,v in pairs(rednetResults) do
  1763. v.close()
  1764. end
  1765. for k,v in pairs(directResults) do
  1766. if k ~= ignoreDirect then
  1767. v.close()
  1768. end
  1769. end
  1770. end
  1771.  
  1772. local timer = os.startTimer(initiationTimeout)
  1773.  
  1774. Modem.open(serverChannel)
  1775. Modem.transmit(serverChannel, requestHeader .. serializedHandshake)
  1776.  
  1777. rednet.broadcast(requestHeader .. serializedHandshake, header.rednetHeader .. serverChannel)
  1778.  
  1779. -- Extendable to have server selection
  1780.  
  1781. while true do
  1782. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1783. if event == "modem_message" then
  1784. local fullMatch = responseMatch .. tostring(dist) .. header.responseMatchC
  1785. if channel == serverChannel and message:match(fullMatch) and type(textutils.unserialize(message:match(fullMatch))) == "table" then
  1786. local key = Handshake.generateResponseData(textutils.unserialize(message:match(fullMatch)))
  1787. if key then
  1788. local connection = SecureConnection.new(key, url, url, dist)
  1789. table.insert(directResults, {
  1790. connection = connection,
  1791. fetchPage = function(page)
  1792. if not connection then
  1793. return nil
  1794. end
  1795.  
  1796. local fetchTimer = os.startTimer(fetchTimeout)
  1797.  
  1798. local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1799. local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1800.  
  1801. connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1802.  
  1803. while true do
  1804. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1805. if event == "modem_message" and channel == connection.channel and connection:verifyHeader(message) then
  1806. local resp, data = connection:decryptMessage(message)
  1807. if not resp then
  1808. -- Decryption error
  1809. elseif data and data ~= page then
  1810. if data:match(pageResponseMatch) then
  1811. local head, body = data:match(pageResponseMatch)
  1812. return body, textutils.unserialize(head)
  1813. end
  1814. end
  1815. elseif event == "timer" and id == fetchTimer then
  1816. return nil
  1817. end
  1818. end
  1819. end,
  1820. close = function()
  1821. if connection ~= nil then
  1822. connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1823. Modem.close(connection.channel)
  1824. connection = nil
  1825. end
  1826. end
  1827. })
  1828.  
  1829. disconnectOthers(1)
  1830. return directResults[1]
  1831. end
  1832. end
  1833. elseif event == "rednet_message" then
  1834. local fullMatch = responseMatch .. os.getComputerID() .. header.responseMatchC
  1835. if protocol and tonumber(protocol:match(header.rednetMatch)) == serverChannel and channel:match(fullMatch) and type(textutils.unserialize(channel:match(fullMatch))) == "table" then
  1836. local key = Handshake.generateResponseData(textutils.unserialize(channel:match(fullMatch)))
  1837. if key then
  1838. local connection = SecureConnection.new(key, url, url, id, true)
  1839. table.insert(rednetResults, {
  1840. connection = connection,
  1841. fetchPage = function(page)
  1842. if not connection then
  1843. return nil
  1844. end
  1845.  
  1846. local fetchTimer = os.startTimer(fetchTimeout)
  1847.  
  1848. local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1849. local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1850.  
  1851. connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1852.  
  1853. while true do
  1854. local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1855. if event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == connection.channel and connection:verifyHeader(channel) then
  1856. local resp, data = connection:decryptMessage(channel)
  1857. if not resp then
  1858. -- Decryption error
  1859. elseif data and data ~= page then
  1860. if data:match(pageResponseMatch) then
  1861. local head, body = data:match(pageResponseMatch)
  1862. return body, textutils.unserialize(head)
  1863. end
  1864. end
  1865. elseif event == "timer" and id == fetchTimer then
  1866. return nil
  1867. end
  1868. end
  1869. end,
  1870. close = function()
  1871. connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1872. Modem.close(connection.channel)
  1873. connection = nil
  1874. end
  1875. })
  1876.  
  1877. if #rednetResults == 1 then
  1878. timer = os.startTimer(0.2)
  1879. end
  1880. end
  1881. end
  1882. elseif event == "timer" and id == timer then
  1883. -- Return
  1884. if #directResults > 0 then
  1885. disconnectOthers(1)
  1886. return directResults[1]
  1887. elseif #rednetResults > 0 then
  1888. local lowestID = math.huge
  1889. local lowestResult = nil
  1890. for k,v in pairs(rednetResults) do
  1891. if v.connection.rednet_id < lowestID then
  1892. lowestID = v.connection.rednet_id
  1893. lowestResult = v
  1894. end
  1895. end
  1896.  
  1897. for k,v in pairs(rednetResults) do
  1898. if v.connection.rednet_id ~= lowestID then
  1899. v.close()
  1900. end
  1901. end
  1902.  
  1903. return lowestResult
  1904. else
  1905. return nil
  1906. end
  1907. end
  1908. end
  1909. end
  1910.  
  1911.  
  1912.  
  1913. -- Fetching Raw Data
  1914.  
  1915.  
  1916. local fetchSearchResultsForQuery = function(query)
  1917. local all = protocols[currentProtocol]["fetchAllSearchResults"]()
  1918. local results = {}
  1919. if query and query:len() > 0 then
  1920. for _, v in pairs(all) do
  1921. if v:find(query:lower()) then
  1922. table.insert(results, v)
  1923. end
  1924. end
  1925. else
  1926. results = all
  1927. end
  1928.  
  1929. table.sort(results)
  1930. return results
  1931. end
  1932.  
  1933.  
  1934. local getConnectionObjectFromURL = function(url)
  1935. local domain = url:match("^([^/]+)")
  1936. return protocols[currentProtocol]["fetchConnectionObject"](domain)
  1937. end
  1938.  
  1939.  
  1940. local determineLanguage = function(header)
  1941. if type(header) == "table" then
  1942. if header.language and header.language == "Firewolf Markup" then
  1943. return "fwml"
  1944. else
  1945. return "lua"
  1946. end
  1947. else
  1948. return "lua"
  1949. end
  1950. end
  1951.  
  1952.  
  1953.  
  1954. -- History
  1955.  
  1956.  
  1957. local appendToHistory = function(url)
  1958. if history[1] ~= url then
  1959. table.insert(history, 1, url)
  1960. end
  1961. end
  1962.  
  1963.  
  1964.  
  1965. -- Fetch Websites
  1966.  
  1967.  
  1968. local loadingAnimation = function()
  1969. local state = -2
  1970.  
  1971. term.setTextColor(theme.text)
  1972. term.setBackgroundColor(theme.accent)
  1973.  
  1974. term.setCursorPos(w - 5, 1)
  1975. term.write("[= ]")
  1976.  
  1977. local timer = os.startTimer(animationInterval)
  1978.  
  1979. while true do
  1980. local event, timerID = os.pullEvent()
  1981. if event == "timer" and timerID == timer then
  1982. term.setTextColor(theme.text)
  1983. term.setBackgroundColor(theme.accent)
  1984.  
  1985. state = state + 1
  1986.  
  1987. term.setCursorPos(w - 5, 1)
  1988. term.write("[ ]")
  1989. term.setCursorPos(w - 2 - math.abs(state), 1)
  1990. term.write("=")
  1991.  
  1992. if state == 2 then
  1993. state = -2
  1994. end
  1995.  
  1996. timer = os.startTimer(animationInterval)
  1997. end
  1998. end
  1999. end
  2000.  
  2001.  
  2002. local normalizeURL = function(url)
  2003. url = url:lower():gsub(" ", "")
  2004. if url == "home" or url == "homepage" then
  2005. url = "firewolf"
  2006. end
  2007.  
  2008. return url
  2009. end
  2010.  
  2011.  
  2012. local normalizePage = function(page)
  2013. if not page then page = "" end
  2014. page = page:lower()
  2015. if page == "" then
  2016. page = "/"
  2017. end
  2018. return page
  2019. end
  2020.  
  2021.  
  2022. local determineActionForURL = function(url)
  2023. if url:len() > 0 and url:gsub("/", ""):len() == 0 then
  2024. return "none"
  2025. end
  2026.  
  2027. if url == "exit" then
  2028. return "exit"
  2029. elseif builtInSites["display"][url] then
  2030. return "internal website"
  2031. elseif url == "" then
  2032. local results = fetchSearchResultsForQuery()
  2033. if #results > 0 then
  2034. return "search", results
  2035. else
  2036. return "none"
  2037. end
  2038. else
  2039. local connection = getConnectionObjectFromURL(url)
  2040. if connection then
  2041. return "external website", connection
  2042. else
  2043. local results = fetchSearchResultsForQuery(url)
  2044. if #results > 0 then
  2045. return "search", results
  2046. else
  2047. return "none"
  2048. end
  2049. end
  2050. end
  2051. end
  2052.  
  2053.  
  2054. local fetchSearch = function(url, results)
  2055. return languages["lua"]["runWithoutAntivirus"](builtInSites["search"], results)
  2056. end
  2057.  
  2058.  
  2059. local fetchInternal = function(url)
  2060. return languages["lua"]["runWithoutAntivirus"](builtInSites["display"][url])
  2061. end
  2062.  
  2063.  
  2064. local fetchError = function(err)
  2065. return languages["lua"]["runWithoutAntivirus"](builtInSites["error"], err)
  2066. end
  2067.  
  2068.  
  2069. local fetchExternal = function(url, connection)
  2070. if connection.multipleServers then
  2071. -- Please forgive me
  2072. -- GravityScore forced me to do it like this
  2073. -- I don't mean it, I really don't.
  2074. connection = connection.servers[1]
  2075. end
  2076.  
  2077. local page = normalizePage(url:match("^[^/]+/(.+)"))
  2078. local contents, head = connection.fetchPage(page)
  2079. if contents then
  2080. if type(contents) ~= "string" then
  2081. return fetchNone()
  2082. else
  2083. local language = determineLanguage(head)
  2084. return languages[language]["run"](contents, page, connection)
  2085. end
  2086. else
  2087. if connection then
  2088. connection.close()
  2089. return "retry"
  2090. end
  2091. return fetchError("A connection error/timeout has occurred!")
  2092. end
  2093. end
  2094.  
  2095.  
  2096. local fetchNone = function()
  2097. return languages["lua"]["runWithoutAntivirus"](builtInSites["noresults"])
  2098. end
  2099.  
  2100.  
  2101. local fetchURL = function(url, inheritConnection)
  2102. url = normalizeURL(url)
  2103. currentWebsiteURL = url
  2104.  
  2105. if inheritConnection then
  2106. local resp = fetchExternal(url, inheritConnection)
  2107. if resp ~= "retry" then
  2108. return resp, false, inheritConnection
  2109. end
  2110. end
  2111.  
  2112. local action, connection = determineActionForURL(url)
  2113.  
  2114. if action == "search" then
  2115. return fetchSearch(url, connection), true
  2116. elseif action == "internal website" then
  2117. return fetchInternal(url), true
  2118. elseif action == "external website" then
  2119. local resp = fetchExternal(url, connection)
  2120. if resp == "retry" then
  2121. return fetchError("A connection error/timeout has occurred!"), false, connection
  2122. else
  2123. return resp, false, connection
  2124. end
  2125. elseif action == "none" then
  2126. return fetchNone(), true
  2127. elseif action == "exit" then
  2128. os.queueEvent("terminate")
  2129. end
  2130.  
  2131. return nil
  2132. end
  2133.  
  2134.  
  2135.  
  2136. -- Tabs
  2137.  
  2138.  
  2139. local switchTab = function(index, shouldntResume)
  2140. if not tabs[index] then
  2141. return
  2142. end
  2143.  
  2144. if tabs[currentTab].win then
  2145. tabs[currentTab].win.setVisible(false)
  2146. end
  2147.  
  2148. currentTab = index
  2149. isMenubarOpen = tabs[currentTab].isMenubarOpen
  2150. currentWebsiteURL = tabs[currentTab].url
  2151.  
  2152. term.redirect(originalTerminal)
  2153. clear(theme.background, theme.text)
  2154. drawMenubar()
  2155.  
  2156. term.redirect(tabs[currentTab].win)
  2157. term.setCursorPos(1, 1)
  2158. tabs[currentTab].win.setVisible(true)
  2159. tabs[currentTab].win.redraw()
  2160.  
  2161. if not shouldntResume then
  2162. coroutine.resume(tabs[currentTab].thread)
  2163. end
  2164. end
  2165.  
  2166.  
  2167. local closeCurrentTab = function()
  2168. if #tabs <= 0 then
  2169. return
  2170. end
  2171.  
  2172. table.remove(tabs, currentTab)
  2173.  
  2174. currentTab = math.max(currentTab - 1, 1)
  2175. switchTab(currentTab, true)
  2176. end
  2177.  
  2178.  
  2179. local loadTab = function(index, url, givenFunc)
  2180. url = normalizeURL(url)
  2181.  
  2182. local func = nil
  2183. local isOpen = true
  2184. local currentConnection = false
  2185.  
  2186. isMenubarOpen = true
  2187. currentWebsiteURL = url
  2188. drawMenubar()
  2189.  
  2190. if tabs[index] and tabs[index].connection and tabs[index].url then
  2191. if url:match("^([^/]+)") == tabs[index].url:match("^([^/]+)") then
  2192. currentConnection = tabs[index].connection
  2193. else
  2194. tabs[index].connection.close()
  2195. tabs[index].connection = nil
  2196. end
  2197. end
  2198.  
  2199. if givenFunc then
  2200. func = givenFunc
  2201. else
  2202. parallel.waitForAny(function()
  2203. func, isOpen, connection = fetchURL(url, currentConnection)
  2204. end, function()
  2205. while true do
  2206. local event, key = os.pullEvent()
  2207. if event == "key" and (key == 29 or key == 157) then
  2208. break
  2209. end
  2210. end
  2211. end, loadingAnimation)
  2212. end
  2213.  
  2214. if func then
  2215. appendToHistory(url)
  2216.  
  2217. tabs[index] = {}
  2218. tabs[index].url = url
  2219. tabs[index].connection = connection
  2220. tabs[index].win = window.create(originalTerminal, 1, 1, w, h, false)
  2221.  
  2222. tabs[index].thread = coroutine.create(func)
  2223. tabs[index].isMenubarOpen = isOpen
  2224. tabs[index].isMenubarPermanent = isOpen
  2225.  
  2226. tabs[index].ox = 1
  2227. tabs[index].oy = 1
  2228.  
  2229. term.redirect(tabs[index].win)
  2230. clear(theme.background, theme.text)
  2231.  
  2232. switchTab(index)
  2233. end
  2234. end
  2235.  
  2236.  
  2237.  
  2238. -- Website Environments
  2239.  
  2240.  
  2241. local getWhitelistedEnvironment = function()
  2242. local env = {}
  2243.  
  2244. local function copy(source, destination, key)
  2245. destination[key] = {}
  2246. for k, v in pairs(source) do
  2247. destination[key][k] = v
  2248. end
  2249. end
  2250.  
  2251. copy(bit, env, "bit")
  2252. copy(colors, env, "colors")
  2253. copy(colours, env, "colours")
  2254. copy(coroutine, env, "coroutine")
  2255.  
  2256. copy(disk, env, "disk")
  2257. env["disk"]["setLabel"] = nil
  2258. env["disk"]["eject"] = nil
  2259.  
  2260. copy(gps, env, "gps")
  2261. copy(help, env, "help")
  2262. copy(keys, env, "keys")
  2263. copy(math, env, "math")
  2264.  
  2265. copy(os, env, "os")
  2266. env["os"]["run"] = nil
  2267. env["os"]["shutdown"] = nil
  2268. env["os"]["reboot"] = nil
  2269. env["os"]["setComputerLabel"] = nil
  2270. env["os"]["queueEvent"] = nil
  2271. env["os"]["pullEvent"] = function(filter)
  2272. while true do
  2273. local event = {os.pullEvent(filter)}
  2274. if not filter then
  2275. return unpack(event)
  2276. elseif filter and event[1] == filter then
  2277. return unpack(event)
  2278. end
  2279. end
  2280. end
  2281. env["os"]["pullEventRaw"] = env["os"]["pullEvent"]
  2282.  
  2283. copy(paintutils, env, "paintutils")
  2284. copy(parallel, env, "parallel")
  2285. copy(peripheral, env, "peripheral")
  2286. copy(rednet, env, "rednet")
  2287. copy(redstone, env, "redstone")
  2288. copy(redstone, env, "rs")
  2289.  
  2290. copy(shell, env, "shell")
  2291. env["shell"]["run"] = nil
  2292. env["shell"]["exit"] = nil
  2293. env["shell"]["setDir"] = nil
  2294. env["shell"]["setAlias"] = nil
  2295. env["shell"]["clearAlias"] = nil
  2296. env["shell"]["setPath"] = nil
  2297.  
  2298. copy(string, env, "string")
  2299. copy(table, env, "table")
  2300.  
  2301. copy(term, env, "term")
  2302. env["term"]["redirect"] = nil
  2303. env["term"]["restore"] = nil
  2304.  
  2305. copy(textutils, env, "textutils")
  2306. copy(vector, env, "vector")
  2307.  
  2308. if turtle then
  2309. copy(turtle, env, "turtle")
  2310. end
  2311.  
  2312. if http then
  2313. copy(http, env, "http")
  2314. end
  2315.  
  2316. env["assert"] = assert
  2317. env["printError"] = printError
  2318. env["tonumber"] = tonumber
  2319. env["tostring"] = tostring
  2320. env["type"] = type
  2321. env["next"] = next
  2322. env["unpack"] = unpack
  2323. env["pcall"] = pcall
  2324. env["xpcall"] = xpcall
  2325. env["sleep"] = sleep
  2326. env["pairs"] = pairs
  2327. env["ipairs"] = ipairs
  2328. env["read"] = read
  2329. env["write"] = write
  2330. env["select"] = select
  2331. env["print"] = print
  2332. env["setmetatable"] = setmetatable
  2333. env["getmetatable"] = getmetatable
  2334.  
  2335. env["_G"] = env
  2336.  
  2337. return env
  2338. end
  2339.  
  2340.  
  2341. local overrideEnvironment = function(env)
  2342. local localTerm = {}
  2343. for k, v in pairs(term) do
  2344. localTerm[k] = v
  2345. end
  2346.  
  2347. env["term"]["clear"] = function()
  2348. localTerm.clear()
  2349. drawMenubar()
  2350. end
  2351.  
  2352. env["term"]["scroll"] = function(n)
  2353. localTerm.scroll(n)
  2354. drawMenubar()
  2355. end
  2356.  
  2357. env["shell"]["getRunningProgram"] = function()
  2358. return currentWebsiteURL
  2359. end
  2360. end
  2361.  
  2362. local urlEncode = function(url)
  2363. local result = url
  2364.  
  2365. result = result:gsub("%%", "%%a")
  2366. result = result:gsub(":", "%%c")
  2367. result = result:gsub("/", "%%s")
  2368. result = result:gsub("\n", "%%n")
  2369. result = result:gsub(" ", "%%w")
  2370. result = result:gsub("&", "%%m")
  2371. result = result:gsub("%?", "%%q")
  2372. result = result:gsub("=", "%%e")
  2373. result = result:gsub("%.", "%%d")
  2374.  
  2375. return result
  2376. end
  2377.  
  2378. local urlDecode = function(url)
  2379. local result = url
  2380.  
  2381. result = result:gsub("%%c", ":")
  2382. result = result:gsub("%%s", "/")
  2383. result = result:gsub("%%n", "\n")
  2384. result = result:gsub("%%w", " ")
  2385. result = result:gsub("%%&", "&")
  2386. result = result:gsub("%%q", "%?")
  2387. result = result:gsub("%%e", "=")
  2388. result = result:gsub("%%d", "%.")
  2389. result = result:gsub("%%m", "%%")
  2390.  
  2391. return result
  2392. end
  2393.  
  2394. local applyAPIFunctions = function(env, connection)
  2395. env["firewolf"] = {}
  2396. env["firewolf"]["version"] = version
  2397. env["firewolf"]["domain"] = currentWebsiteURL:match("^[^/]+")
  2398.  
  2399. env["firewolf"]["redirect"] = function(url)
  2400. if type(url) ~= "string" then
  2401. return error("string (url) expected, got " .. type(url))
  2402. end
  2403.  
  2404. os.queueEvent(redirectEvent, url)
  2405. coroutine.yield()
  2406. end
  2407.  
  2408. env["firewolf"]["download"] = function(page)
  2409. if type(page) ~= "string" then
  2410. return error("string (page) expected")
  2411. end
  2412. local bannedNames = {"ls", "dir", "delete", "copy", "move", "list", "rm", "cp", "mv", "clear", "cd", "lua"}
  2413.  
  2414. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2415. if startSearch == 1 then
  2416. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2417. page = page:sub(endSearch + 2, -1)
  2418. else
  2419. page = page:sub(endSearch + 1, -1)
  2420. end
  2421. end
  2422.  
  2423. local filename = page:match("([^/]+)$")
  2424. if not filename then
  2425. return false, "Cannot download index"
  2426. end
  2427.  
  2428. for k, v in pairs(bannedNames) do
  2429. if filename == v then
  2430. return false, "Filename prohibited!"
  2431. end
  2432. end
  2433.  
  2434. if not fs.exists(downloadsLocation) then
  2435. fs.makeDir(downloadsLocation)
  2436. elseif not fs.isDir(downloadsLocation) then
  2437. return false, "Downloads disabled!"
  2438. end
  2439.  
  2440. contents = connection.fetchPage(normalizePage(page))
  2441. if type(contents) ~= "string" then
  2442. return false, "Download error!"
  2443. else
  2444. local f = io.open(downloadsLocation .. "/" .. filename, "w")
  2445. f:write(contents)
  2446. f:close()
  2447. return true, downloadsLocation .. "/" .. filename
  2448. end
  2449. end
  2450.  
  2451. env["firewolf"]["encode"] = function(vars)
  2452. if type(vars) ~= "table" then
  2453. return error("table (vars) expected, got " .. type(vars))
  2454. end
  2455.  
  2456. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2457. if startSearch == 1 then
  2458. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2459. page = page:sub(endSearch + 2, -1)
  2460. else
  2461. page = page:sub(endSearch + 1, -1)
  2462. end
  2463. end
  2464.  
  2465. local construct = "?"
  2466. for k,v in pairs(vars) do
  2467. construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2468. end
  2469. -- Get rid of that last ampersand
  2470. construct = construct:sub(1, -2)
  2471.  
  2472. return construct
  2473. end
  2474.  
  2475. env["firewolf"]["query"] = function(page, vars)
  2476. if type(page) ~= "string" then
  2477. return error("string (page) expected, got " .. type(page))
  2478. end
  2479. if vars and type(vars) ~= "table" then
  2480. return error("table (vars) expected, got " .. type(vars))
  2481. end
  2482.  
  2483. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2484. if startSearch == 1 then
  2485. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2486. page = page:sub(endSearch + 2, -1)
  2487. else
  2488. page = page:sub(endSearch + 1, -1)
  2489. end
  2490. end
  2491.  
  2492. local construct = page .. "?"
  2493. if vars then
  2494. for k,v in pairs(vars) do
  2495. construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2496. end
  2497. end
  2498. -- Get rid of that last ampersand
  2499. construct = construct:sub(1, -2)
  2500.  
  2501. contents = connection.fetchPage(normalizePage(construct))
  2502. if type(contents) == "string" then
  2503. return contents
  2504. else
  2505. return false
  2506. end
  2507. end
  2508.  
  2509. env["firewolf"]["loadImage"] = function(page)
  2510. if type(page) ~= "string" then
  2511. return error("string (page) expected, got " .. type(page))
  2512. end
  2513.  
  2514. local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2515. if startSearch == 1 then
  2516. if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2517. page = page:sub(endSearch + 2, -1)
  2518. else
  2519. page = page:sub(endSearch + 1, -1)
  2520. end
  2521. end
  2522.  
  2523. local filename = page:match("([^/]+)$")
  2524. if not filename then
  2525. return false, "Cannot load index as an image!"
  2526. end
  2527.  
  2528. contents = connection.fetchPage(normalizePage(page))
  2529. if type(contents) ~= "string" then
  2530. return false, "Download error!"
  2531. else
  2532. local colorLookup = {}
  2533. for n = 1, 16 do
  2534. colorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
  2535. end
  2536.  
  2537. local image = {}
  2538. for line in contents:gmatch("[^\n]+") do
  2539. local lines = {}
  2540. for x = 1, line:len() do
  2541. lines[x] = colorLookup[string.byte(line, x, x)] or 0
  2542. end
  2543. table.insert(image, lines)
  2544. end
  2545.  
  2546. return image
  2547. end
  2548. end
  2549.  
  2550. env["center"] = center
  2551. env["fill"] = fill
  2552. end
  2553.  
  2554.  
  2555. local getWebsiteEnvironment = function(antivirus, connection)
  2556. local env = {}
  2557.  
  2558. if antivirus then
  2559. env = getWhitelistedEnvironment()
  2560. overrideEnvironment(env)
  2561. else
  2562. setmetatable(env, {__index = _G})
  2563. end
  2564.  
  2565. applyAPIFunctions(env, connection)
  2566.  
  2567. return env
  2568. end
  2569.  
  2570.  
  2571.  
  2572. -- FWML Execution
  2573.  
  2574.  
  2575. local render = {}
  2576.  
  2577. render["functions"] = {}
  2578. render["functions"]["public"] = {}
  2579. render["alignations"] = {}
  2580.  
  2581. render["variables"] = {
  2582. scroll,
  2583. maxScroll,
  2584. align,
  2585. linkData = {},
  2586. blockLength,
  2587. link,
  2588. linkStart,
  2589. markers,
  2590. currentOffset,
  2591. }
  2592.  
  2593.  
  2594. local function getLine(loc, data)
  2595. local _, changes = data:sub(1, loc):gsub("\n", "")
  2596. if not changes then
  2597. return 1
  2598. else
  2599. return changes + 1
  2600. end
  2601. end
  2602.  
  2603.  
  2604. local function parseData(data)
  2605. local commands = {}
  2606. local searchPos = 1
  2607.  
  2608. while #data > 0 do
  2609. local sCmd, eCmd = data:find("%[[^%]]+%]", searchPos)
  2610. if sCmd then
  2611. sCmd = sCmd + 1
  2612. eCmd = eCmd - 1
  2613.  
  2614. if (sCmd > 2) then
  2615. if data:sub(sCmd - 2, sCmd - 2) == "\\" then
  2616. local t = data:sub(searchPos, sCmd - 1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2617. if #t > 0 then
  2618. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2619. commands[#commands][1] = commands[#commands][1] .. t
  2620. else
  2621. table.insert(commands, {t})
  2622. end
  2623. end
  2624. searchPos = sCmd
  2625. else
  2626. local t = data:sub(searchPos, sCmd - 2):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2627. if #t > 0 then
  2628. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2629. commands[#commands][1] = commands[#commands][1] .. t
  2630. else
  2631. table.insert(commands, {t})
  2632. end
  2633. end
  2634.  
  2635. t = data:sub(sCmd, eCmd):gsub("\n", "")
  2636. table.insert(commands, {getLine(sCmd, data), t})
  2637. searchPos = eCmd + 2
  2638. end
  2639. else
  2640. local t = data:sub(sCmd, eCmd):gsub("\n", "")
  2641. table.insert(commands, {getLine(sCmd, data), t})
  2642. searchPos = eCmd + 2
  2643. end
  2644. else
  2645. local t = data:sub(searchPos, -1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2646. if #t > 0 then
  2647. if #commands > 0 and type(commands[#commands][1]) == "string" then
  2648. commands[#commands][1] = commands[#commands][1] .. t
  2649. else
  2650. table.insert(commands, {t})
  2651. end
  2652. end
  2653.  
  2654. break
  2655. end
  2656. end
  2657.  
  2658. return commands
  2659. end
  2660.  
  2661.  
  2662. local function proccessData(commands)
  2663. searchIndex = 0
  2664.  
  2665. while searchIndex < #commands do
  2666. searchIndex = searchIndex + 1
  2667.  
  2668. local length = 0
  2669. local origin = searchIndex
  2670.  
  2671. if type(commands[searchIndex][1]) == "string" then
  2672. length = length + #commands[searchIndex][1]
  2673. local endIndex = origin
  2674. for i = origin + 1, #commands do
  2675. if commands[i][2] then
  2676. local command = commands[i][2]:match("^(%w+)%s-")
  2677. if not (command == "c" or command == "color" or command == "bg"
  2678. or command == "background" or command == "newlink" or command == "endlink") then
  2679. endIndex = i
  2680. break
  2681. end
  2682. elseif commands[i][2] then
  2683.  
  2684. else
  2685. length = length + #commands[i][1]
  2686. end
  2687. if i == #commands then
  2688. endIndex = i
  2689. end
  2690. end
  2691.  
  2692. commands[origin][2] = length
  2693. searchIndex = endIndex
  2694. length = 0
  2695. end
  2696. end
  2697.  
  2698. return commands
  2699. end
  2700.  
  2701.  
  2702. local function parse(original)
  2703. return proccessData(parseData(original))
  2704. end
  2705.  
  2706.  
  2707. render["functions"]["display"] = function(text, length, offset, center)
  2708. if not offset then
  2709. offset = 0
  2710. end
  2711.  
  2712. return render.variables.align(text, length, w, offset, center);
  2713. end
  2714.  
  2715.  
  2716. render["functions"]["displayText"] = function(source)
  2717. if source[2] then
  2718. render.variables.blockLength = source[2]
  2719. if render.variables.link and not render.variables.linkStart then
  2720. render.variables.linkStart = render.functions.display(
  2721. source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2722. else
  2723. render.functions.display(source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2724. end
  2725. else
  2726. if render.variables.link and not render.variables.linkStart then
  2727. render.variables.linkStart = render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2728. else
  2729. render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2730. end
  2731. end
  2732. end
  2733.  
  2734.  
  2735. render["functions"]["public"]["br"] = function(source)
  2736. if render.variables.link then
  2737. return "Cannot insert new line within a link on line " .. source[1]
  2738. end
  2739.  
  2740. render.variables.scroll = render.variables.scroll + 1
  2741. render.variables.maxScroll = math.max(render.variables.scroll, render.variables.maxScroll)
  2742. end
  2743.  
  2744.  
  2745. render["functions"]["public"]["c "] = function(source)
  2746. local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2747. if colors[sColor] then
  2748. term.setTextColor(colors[sColor])
  2749. else
  2750. return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2751. end
  2752. end
  2753.  
  2754.  
  2755. render["functions"]["public"]["color "] = render["functions"]["public"]["c "]
  2756.  
  2757.  
  2758. render["functions"]["public"]["bg "] = function(source)
  2759. local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2760. if colors[sColor] then
  2761. term.setBackgroundColor(colors[sColor])
  2762. else
  2763. return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2764. end
  2765. end
  2766.  
  2767.  
  2768. render["functions"]["public"]["background "] = render["functions"]["public"]["bg "]
  2769.  
  2770.  
  2771. render["functions"]["public"]["newlink "] = function(source)
  2772. if render.variables.link then
  2773. return "Cannot nest links on line " .. source[1]
  2774. end
  2775.  
  2776. render.variables.link = source[2]:match("^%w+%s+(.+)$") or ""
  2777. render.variables.linkStart = false
  2778. end
  2779.  
  2780.  
  2781. render["functions"]["public"]["endlink"] = function(source)
  2782. if not render.variables.link then
  2783. return "Cannot end a link without a link on line " .. source[1]
  2784. end
  2785.  
  2786. local linkEnd = term.getCursorPos()-1
  2787. table.insert(render.variables.linkData, {render.variables.linkStart,
  2788. linkEnd, render.variables.scroll, render.variables.link})
  2789. render.variables.link = false
  2790. render.variables.linkStart = false
  2791. end
  2792.  
  2793.  
  2794. render["functions"]["public"]["offset "] = function(source)
  2795. local offset = tonumber((source[2]:match("^%w+%s+(.+)$") or ""))
  2796. if offset then
  2797. render.variables.currentOffset = offset
  2798. else
  2799. return "Invalid offset value: \"" .. (source[2]:match("^%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2800. end
  2801. end
  2802.  
  2803.  
  2804. render["functions"]["public"]["marker "] = function(source)
  2805. render.variables.markers[(source[2]:match("^%w+%s+(.+)$") or "")] = render.variables.scroll
  2806. end
  2807.  
  2808.  
  2809. render["functions"]["public"]["goto "] = function(source)
  2810. local location = source[2]:match("%w+%s+(.+)$")
  2811. if render.variables.markers[location] then
  2812. render.variables.scroll = render.variables.markers[location]
  2813. else
  2814. return "No such location: \"" .. (source[2]:match("%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2815. end
  2816. end
  2817.  
  2818.  
  2819. render["functions"]["public"]["box "] = function(source)
  2820. local sColor, align, height, width, offset, url = source[2]:match("^box (%a+) (%a+) (%-?%d+) (%-?%d+) (%-?%d+) ?([^ ]*)")
  2821. if not sColor then
  2822. return "Invalid box syntax on line " .. source[1]
  2823. end
  2824.  
  2825. local x, y = term.getCursorPos()
  2826. local startX
  2827.  
  2828. if align == "center" or align == "centre" then
  2829. startX = math.ceil((w / 2) - width / 2) + offset
  2830. elseif align == "left" then
  2831. startX = 1 + offset
  2832. elseif align == "right" then
  2833. startX = (w - width + 1) + offset
  2834. else
  2835. return "Invalid align option for box on line " .. source[1]
  2836. end
  2837.  
  2838. if not colors[sColor] then
  2839. return "Invalid color: \"" .. sColor .. "\" for box on line " .. source[1]
  2840. end
  2841.  
  2842. term.setBackgroundColor(colors[sColor])
  2843. for i = 0, height - 1 do
  2844. term.setCursorPos(startX, render.variables.scroll + i)
  2845. term.write(string.rep(" ", width))
  2846. if url:len() > 3 then
  2847. table.insert(render.variables.linkData, {startX, startX + width - 1, render.variables.scroll + i, url})
  2848. end
  2849. end
  2850.  
  2851. render.variables.maxScroll = math.max(render.variables.scroll + height - 1, render.variables.maxScroll)
  2852. term.setCursorPos(x, y)
  2853. end
  2854.  
  2855.  
  2856. render["alignations"]["left"] = function(text, length, _, offset)
  2857. local x, y = term.getCursorPos()
  2858. if length then
  2859. term.setCursorPos(1 + offset, render.variables.scroll)
  2860. term.write(text)
  2861. return 1 + offset
  2862. else
  2863. term.setCursorPos(x, render.variables.scroll)
  2864. term.write(text)
  2865. return x
  2866. end
  2867. end
  2868.  
  2869.  
  2870. render["alignations"]["right"] = function(text, length, width, offset)
  2871. local x, y = term.getCursorPos()
  2872. if length then
  2873. term.setCursorPos((width - length + 1) + offset, render.variables.scroll)
  2874. term.write(text)
  2875. return (width - length + 1) + offset
  2876. else
  2877. term.setCursorPos(x, render.variables.scroll)
  2878. term.write(text)
  2879. return x
  2880. end
  2881. end
  2882.  
  2883.  
  2884. render["alignations"]["center"] = function(text, length, _, offset, center)
  2885. local x, y = term.getCursorPos()
  2886. if length then
  2887. term.setCursorPos(math.ceil(center - length / 2) + offset, render.variables.scroll)
  2888. term.write(text)
  2889. return math.ceil(center - length / 2) + offset
  2890. else
  2891. term.setCursorPos(x, render.variables.scroll)
  2892. term.write(text)
  2893. return x
  2894. end
  2895. end
  2896.  
  2897.  
  2898. render["render"] = function(data, startScroll)
  2899. if startScroll == nil then
  2900. render.variables.startScroll = 0
  2901. else
  2902. render.variables.startScroll = startScroll
  2903. end
  2904.  
  2905. render.variables.scroll = startScroll + 1
  2906. render.variables.maxScroll = render.variables.scroll
  2907.  
  2908. render.variables.linkData = {}
  2909.  
  2910. render.variables.align = render.alignations.left
  2911.  
  2912. render.variables.blockLength = 0
  2913. render.variables.link = false
  2914. render.variables.linkStart = false
  2915. render.variables.markers = {}
  2916. render.variables.currentOffset = 0
  2917.  
  2918. for k, v in pairs(data) do
  2919. if type(v[2]) ~= "string" then
  2920. render.functions.displayText(v)
  2921. elseif v[2] == "<" or v[2] == "left" then
  2922. render.variables.align = render.alignations.left
  2923. elseif v[2] == ">" or v[2] == "right" then
  2924. render.variables.align = render.alignations.right
  2925. elseif v[2] == "=" or v[2] == "center" then
  2926. render.variables.align = render.alignations.center
  2927. else
  2928. local existentFunction = false
  2929.  
  2930. for name, func in pairs(render.functions.public) do
  2931. if v[2]:find(name) == 1 then
  2932. existentFunction = true
  2933. local ret = func(v)
  2934. if ret then
  2935. return ret
  2936. end
  2937. end
  2938. end
  2939.  
  2940. if not existentFunction then
  2941. return "Non-existent tag: \"" .. v[2] .. "\" on line " .. v[1]
  2942. end
  2943. end
  2944. end
  2945.  
  2946. return render.variables.linkData, render.variables.maxScroll - render.variables.startScroll
  2947. end
  2948.  
  2949.  
  2950.  
  2951. -- Lua Execution
  2952.  
  2953.  
  2954. languages["lua"] = {}
  2955. languages["fwml"] = {}
  2956.  
  2957.  
  2958. languages["lua"]["runWithErrorCatching"] = function(func, ...)
  2959. local _, err = pcall(func, ...)
  2960. if err then
  2961. os.queueEvent(websiteErrorEvent, err)
  2962. end
  2963. end
  2964.  
  2965.  
  2966. languages["lua"]["runWithoutAntivirus"] = function(func, ...)
  2967. local args = {...}
  2968. local env = getWebsiteEnvironment(false)
  2969. setfenv(func, env)
  2970. return function()
  2971. languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2972. end
  2973. end
  2974.  
  2975.  
  2976. languages["lua"]["run"] = function(contents, page, connection, ...)
  2977. local func, err = loadstring("sleep(0) " .. contents, page)
  2978. if err then
  2979. return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], err)
  2980. else
  2981. local args = {...}
  2982. local env = getWebsiteEnvironment(true, connection)
  2983. setfenv(func, env)
  2984. return function()
  2985. languages["lua"]["runWithErrorCatching"](func, unpack(args))
  2986. end
  2987. end
  2988. end
  2989.  
  2990.  
  2991. languages["fwml"]["run"] = function(contents, page, connection, ...)
  2992. local err, data = pcall(parse, contents)
  2993. if not err then
  2994. return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], data)
  2995. end
  2996.  
  2997. return function()
  2998. local currentScroll = 0
  2999. local err, links, pageHeight = pcall(render.render, data, currentScroll)
  3000. if type(links) == "string" or not err then
  3001. term.clear()
  3002. os.queueEvent(websiteErrorEvent, links)
  3003. else
  3004. while true do
  3005. local e, scroll, x, y = os.pullEvent()
  3006. if e == "mouse_click" then
  3007. for k, v in pairs(links) do
  3008. if x >= math.min(v[1], v[2]) and x <= math.max(v[1], v[2]) and y == v[3] then
  3009. os.queueEvent(redirectEvent, v[4])
  3010. coroutine.yield()
  3011. end
  3012. end
  3013. elseif e == "mouse_scroll" then
  3014. if currentScroll - scroll - h >= -pageHeight and currentScroll - scroll <= 0 then
  3015. currentScroll = currentScroll - scroll
  3016. clear(theme.background, theme.text)
  3017. links = render.render(data, currentScroll)
  3018. end
  3019. elseif e == "key" and scroll == keys.up or scroll == keys.down then
  3020. local scrollAmount
  3021.  
  3022. if scroll == keys.up then
  3023. scrollAmount = 1
  3024. elseif scroll == keys.down then
  3025. scrollAmount = -1
  3026. end
  3027.  
  3028. local scrollLessHeight = currentScroll + scrollAmount - h >= -pageHeight
  3029. local scrollZero = currentScroll + scrollAmount <= 0
  3030. if scrollLessHeight and scrollZero then
  3031. currentScroll = currentScroll + scrollAmount
  3032. clear(theme.background, theme.text)
  3033. links = render.render(data, currentScroll)
  3034. end
  3035. end
  3036. end
  3037. end
  3038. end
  3039. end
  3040.  
  3041.  
  3042.  
  3043. -- Query Bar
  3044.  
  3045.  
  3046. local readNewWebsiteURL = function()
  3047. local onEvent = function(text, event, key, x, y)
  3048. if event == "mouse_click" then
  3049. if y == 2 then
  3050. local index = determineClickedTab(x, y)
  3051. if index == "new" and #tabs < maxTabs then
  3052. loadTab(#tabs + 1, "firewolf")
  3053. elseif index == "close" then
  3054. closeCurrentTab()
  3055. elseif index then
  3056. switchTab(index)
  3057. end
  3058.  
  3059. return {["nullifyText"] = true, ["exit"] = true}
  3060. elseif y > 2 then
  3061. return {["nullifyText"] = true, ["exit"] = true}
  3062. end
  3063. elseif event == "key" then
  3064. if key == 29 or key == 157 then
  3065. return {["nullifyText"] = true, ["exit"] = true}
  3066. end
  3067. end
  3068. end
  3069.  
  3070. isMenubarOpen = true
  3071. drawMenubar()
  3072. term.setCursorPos(2, 1)
  3073. term.setTextColor(theme.text)
  3074. term.setBackgroundColor(theme.accent)
  3075. term.clearLine()
  3076. term.write(currentProtocol .. "://")
  3077.  
  3078. local website = modifiedRead({
  3079. ["onEvent"] = onEvent,
  3080. ["displayLength"] = w - 9,
  3081. ["history"] = history,
  3082. })
  3083.  
  3084. if not website then
  3085. if not tabs[currentTab].isMenubarPermanent then
  3086. isMenubarOpen = false
  3087. menubarWindow.setVisible(false)
  3088. else
  3089. isMenubarOpen = true
  3090. menubarWindow.setVisible(true)
  3091. end
  3092.  
  3093. term.redirect(tabs[currentTab].win)
  3094. tabs[currentTab].win.setVisible(true)
  3095. tabs[currentTab].win.redraw()
  3096.  
  3097. return
  3098. elseif website == "exit" then
  3099. error()
  3100. end
  3101.  
  3102. loadTab(currentTab, website)
  3103. end
  3104.  
  3105.  
  3106.  
  3107. -- Event Management
  3108.  
  3109.  
  3110. local handleKeyDown = function(event)
  3111. if event[2] == 29 or event[2] == 157 then
  3112. readNewWebsiteURL()
  3113. return true
  3114. end
  3115.  
  3116. return false
  3117. end
  3118.  
  3119.  
  3120. local handleMouseDown = function(event)
  3121. if isMenubarOpen then
  3122. if event[4] == 1 then
  3123. readNewWebsiteURL()
  3124. return true
  3125. elseif event[4] == 2 then
  3126. local index = determineClickedTab(event[3], event[4])
  3127. if index == "new" and #tabs < maxTabs then
  3128. loadTab(#tabs + 1, "firewolf")
  3129. elseif index == "close" then
  3130. closeCurrentTab()
  3131. elseif index then
  3132. switchTab(index)
  3133. end
  3134.  
  3135. return true
  3136. end
  3137. end
  3138.  
  3139. return false
  3140. end
  3141.  
  3142.  
  3143. local handleEvents = function()
  3144. loadTab(1, "firewolf")
  3145. currentTab = 1
  3146.  
  3147. while true do
  3148. drawMenubar()
  3149. local event = {os.pullEventRaw()}
  3150. drawMenubar()
  3151.  
  3152. local cancelEvent = false
  3153. if event[1] == "terminate" then
  3154. break
  3155. elseif event[1] == "key" then
  3156. cancelEvent = handleKeyDown(event)
  3157. elseif event[1] == "mouse_click" then
  3158. cancelEvent = handleMouseDown(event)
  3159. elseif event[1] == websiteErrorEvent then
  3160. cancelEvent = true
  3161.  
  3162. loadTab(currentTab, tabs[currentTab].url, function()
  3163. builtInSites["crash"](event[2])
  3164. end)
  3165. elseif event[1] == redirectEvent then
  3166. cancelEvent = true
  3167.  
  3168. if (event[2]:match("^rdnt://(.+)$")) then
  3169. event[2] = event[2]:match("^rdnt://(.+)$")
  3170. end
  3171.  
  3172. loadTab(currentTab, event[2])
  3173. end
  3174.  
  3175. if not cancelEvent then
  3176. term.redirect(tabs[currentTab].win)
  3177. term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy)
  3178.  
  3179. coroutine.resume(tabs[currentTab].thread, unpack(event))
  3180.  
  3181. local ox, oy = term.getCursorPos()
  3182. tabs[currentTab].ox = ox
  3183. tabs[currentTab].oy = oy
  3184. end
  3185. end
  3186. end
  3187.  
  3188.  
  3189.  
  3190. -- Main
  3191.  
  3192.  
  3193. local main = function()
  3194. currentProtocol = "rdnt"
  3195. currentTab = 1
  3196.  
  3197. if term.isColor() then
  3198. theme = colorTheme
  3199. enableTabBar = true
  3200. else
  3201. theme = grayscaleTheme
  3202. enableTabBar = false
  3203. end
  3204.  
  3205. setupMenubar()
  3206. protocols[currentProtocol]["setup"]()
  3207.  
  3208. clear(theme.background, theme.text)
  3209. handleEvents()
  3210. end
  3211.  
  3212.  
  3213. local handleError = function(err)
  3214. clear(theme.background, theme.text)
  3215.  
  3216. fill(1, 3, w, 3, theme.subtle)
  3217. term.setCursorPos(1, 4)
  3218. center("Firewolf has crashed!")
  3219.  
  3220. term.setBackgroundColor(theme.background)
  3221. term.setCursorPos(1, 8)
  3222. centerSplit(err, w - 4)
  3223. print("\n")
  3224. center("Please report this error to")
  3225. center("GravityScore or 1lann.")
  3226. print("")
  3227. center("Press any key to exit.")
  3228.  
  3229. os.pullEvent("key")
  3230. os.queueEvent("")
  3231. os.pullEvent()
  3232. end
  3233.  
  3234. local _, err = pcall(main)
  3235. term.redirect(originalTerminal)
  3236.  
  3237. Modem.closeAll()
  3238.  
  3239. if err and not err:lower():find("terminate") then
  3240. handleError(err)
  3241. end
  3242.  
  3243.  
  3244. clear(colors.black, colors.white)
  3245. center("Thanks for using Firewolf " .. version)
  3246. center("Made by GravityScore and 1lann")
  3247. print("")
Add Comment
Please, Sign In to add comment