Seren Gemini Server

The old source code from when Seren had a dedicated Gemini server running on a Linode:


const tls = require('tls')
const fs = require('fs')
const path = require('path')
const rootDirectory = 'gmi/'
const fileExists = async path => !!(await fs.promises.stat(path).catch(e => false))

console.log('Seren Server')

var options = {
  key: fs.readFileSync("seren_server_key.pem"),
  cert: fs.readFileSync("seren_server_cert.pem"),
  rejectUnauthorized: true
}

var server = tls.createServer(options, function(socket) {
  socket.on('data', (buf) => {

    let url = new URL(buf.toString())
  
    console.log('Gemini request: ' + url)
  
    var requestPath = url.pathname.substring(1)
  
    if(requestPath.length == 0) requestPath = "index.gmi"
    if(requestPath.endsWith("/")) requestPath = path.join(requestPath, "index.gmi")
    if(requestPath.indexOf('\0') !== -1) {
      errorResponse(socket, 59, 'Poison null byte found: ' + requestPath)
      return
    }

    var entityPath = path.join(rootDirectory, requestPath)

    if (entityPath.indexOf(rootDirectory) !== 0) {
      errorResponse(socket, 59, 'Unsafe path found: ' + entityPath)
      return
    }

    serveFile(socket, entityPath)
  })

  socket.on('error', console.error)
  socket.on('end', () => {
    console.log('Client closed connection')
  })
});

async function serveFile(socket, file){
  const exists = await fileExists(file)

  if(exists){
    const content = await fs.promises.readFile(file);

    let mime
    if(file.endsWith('.gmi')){
      mime = 'text/gemini'
    }else if(file.endsWith('.png')) {
      mime = 'image/png'
    }else if(file.endsWith('.jpg') || file.endsWith('.jpeg')) {
      mime = 'image/jpeg'
    }else if(file.endsWith('.png')) {
      mime = 'image/png'
    }else if(file.endsWith('.svg')) {
      mime = 'image/svg+xml'
    }else if(file.endsWith('.mp3')) {
      mime = 'audio/mpeg'
    }else if(file.endsWith('.xml')){
      mime = 'application/xml'
    }else if(file.endsWith('.apk')){
      mime = 'application/vnd.android.package-archive'
    }else{
      errorResponse(client, 59, '' + file + ' unrecognised file type')
      return
    }

    socket.write('20 ' + mime + '\r\n')
    socket.write(content)
    socket.end()
  }else{
    errorResponse(socket, 51, 'Not found: ' + file)
  }
}

function errorResponse(socket, code, message){
  socket.write('' + code + message + '\r\n')
  socket.end() 
}

server.listen(1965)