// Platform-agnostic Gopher response handler and Gophermap parser // Depends on the platform-specific gopherRequest(resource, input, success, error) function // (defined in the transport.js file here) Hi01379 = (function(psGopherRequest) { var tagsToReplace = {'&': '&', '<': '<', '>': '>'} function esc(str) { // escape HTML entities return str.replace(/[&<>]/g, function(tag) {return tagsToReplace[tag] || tag}) } function unphlow(str) { // Unphlow algorithm implementation var lines=str.split('\n'), line, l = lines.length, i, buf = '', out = [] for(i=0;i 1 ? rest[1] : '' // selector host = rest.length > 2 ? rest[2] : '' // hostname port = rest.length > 3 ? (0|rest[3]) : '' // port // selector defaults to the description only if there are no other fields if(!sel && rest.length < 2) sel = desc if(!host) host = chost // host defaults to the current host if(!port) port = cport // port defaults to the current port if(type == 'i') { // information message - wrap them in pretag if(!preflag) { preflag = true output += '<' + pretag + '>' } output += desc + '\n' } else { //information messages ended, stop preformatting if(preflag) { preflag = false output += '' } if(desc = desc.trim()) { // ignore empty descriptions if(type == '3') // error message output += '' + desc + '' + lbr else if(type == '8') { // output the telnet:// link as is var tlink = 'telnet://' + (sel ? (sel+'@') : '') + host + (port ? (':'+port) : '') output += desc + ': ' + esc(tlink) + lbr } else { // shape the link (internal hi:type|sel|host|port format unless it's an external URL) var deflink = 'hi:'+[type,sel,host,port].join('|'), link = deflink if(type == 'h' && sel.startsWith('URL:')) { link = sel.split(':').slice(1).join(':').trim() if(link.startsWith('javascript:')) // XSS protection link = deflink } output += '' + desc + '' + lbr } } } } return output } // resource format: [type, selector, host, port] function loadHole(resource, input, successCb, errorCb) { var fname = resource[1].split('/').pop(), // as the most intelligent guess ext = fname.split('.').pop().toLowerCase(), // presumed extension extMappings = { // not including AVIF here because they aren't supported by Gecko 48 anyway 'gif': 'image/gif', // including GIF though because nothing prevents using I instead of g 'png': 'image/png', 'svg': 'image/svg+xml', 'webp': 'image/webp', 'apng': 'image/apng', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'jfif': 'image/jpeg', 'pjpeg': 'image/jpeg', 'pjp': 'image/jpeg' } psGopherRequest(resource, input, function(rawdata, type) { if(type == '5' || type == 's' || type == ';' || type == 'd') type = '9' // remap unknown binary types to 9 if(type == '9' || type == 'g' || type == 'I') { // binary file var ctype = 'application/octet-stream' // the default one if everything else fails // update content type if(type == 'g') // GIF-only resource type ctype = 'image/gif' else if(type == 'I') // attempt to guess the image MIME type by the extension ctype = extMappings[ext] var datablob = new Blob([rawdata], {type: ctype}) successCb({ content: datablob, contentType: ctype, contentName: fname, serviceMsg: type + ' ' + resource[1], updateAddr: false }) } else { // assuming text content otherwise var output = (new TextDecoder).decode(rawdata), ctype = 'text/plain', // defaulting to type 0 wo = '' // wrapper-friendly output if(type == '1' || type == '7') { // gophermap output = gophermapToHTML(output, resource[2], resource[3]) ctype = 'text/html' wo = output } else wo = unphlow(output) successCb({ content: output, contentWf: wo, contentType: ctype, serviceMsg: type + ' ' + resource[1] }) } }, errorCb) } return {load: loadHole, render: gophermapToHTML} })(gopherRequest)