Towtow10

Untitled

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