//
// HTTP Daemon
//
// with these updates by D. Finnigan on 12 Nov 15:
// - revised 404 and 400 responses
// - split 200 OK response headers
// - added get_file_info() call
// - check for binary files and set Content-Type accordingly
// still todo: output base filename for Content-Disposition header
//
include "inc/cmdsys.plh"
include "inc/inet.plh"
include "inc/fileio.plh"
include "inc/conio.plh"

word socketHTTP
byte[65]  prefix
word      filebuff
byte[15]  fileInfo = 0 // used for get_file_info()
byte      defhtml = "INDEX.HTML"
byte[200] okhdr // combined response header
//
// HTTP response codes
// For the 4xx codes, if you change the body, make sure to update the Content-Length too
byte httpOK = "HTTP/1.1 200 OK\n\r"
byte httpBAD = "HTTP/1.1 400 Bad Request\n\rContent-Type: text/plain\n\rContent-Length: 17\n\r\n\r400 Bad Request\n\r"
byte httpNOTFOUND = "HTTP/1.1 404 Not Found\n\rStatus: 404 Not Found\n\rContent-Type: text/plain\n\rContent-Length: 28\n\r\n\r404 Error: File not found.\n\r"
//
// HTTP response headers
//
byte httpContentType = "Content-Type: "
byte httpContentLen = "Content-Length: "
byte httpContentAttach = "Content-Disposition: attachment; filename="
byte httpEnd = "\n\r\n\r"
//
// MIME content types
//
byte mimeTextHtml = "text/html"
byte mimeOctetStream = "application/octet-stream"
//
// DEBUG
//
def putip(ipptr)#0
    byte i

    for i = 0 to 2
        puti(ipptr->[i]); putc('.')
    next
    puti(ipptr->[i])
end
def dumpbytes(buf, len)#0
    word i

    for i = 0 to len - 1
        putb(buf->[i])
    if i & 15 == 15
        putln
    else
        putc(' ')
    fin
    next
end
def dumpchars(buf, len)#0
    word i

    len = len - 1
    for i = 0 to len
        putc(buf->[i])
    next
end
//
// String functions
//
def strcat2(dst, src1, src2)
    memcpy(dst + 1,         src1 + 1, ^src1)
    memcpy(dst + 1 + ^src1, src2 + 1, ^src2)
    ^dst = ^src1 + ^src2
    return dst
end
def itos(dst, i)
    if i < 0; ^dst = '-'; i = -i; dst++; fin
    if i < 10
        ^dst = i + '0'
    else
        dst = itos(dst, i / 10)
        ^dst = i % 10 + '0'
    fin
    return dst + 1
end
//
// Send the file contents
//
def sendFile(fd, socket, len)#0
    while isuge(len, 1024)
        fileio:read(fd, filebuff, 1024)
        len = len - 1024
        iNet:sendTCP(socket, filebuff, 1024)
    loop
    if len
        fileio:read(fd, filebuff, len)
        iNet:sendTCP(socket, filebuff, len)
    fin
end
//
// Serve HTTP requests
//
def servHTTP(remip, remport, lclport, data, len, param)
    byte i, refnum, err
    byte[65] filename
    byte[8] lenstr
    word url, filelen

    //
    // Parse HTTP request
    //
    if len > 0
        //dumpchars(data, len)
        //
        // Better be 'GET'
        //
        if data->0 == 'G' and data->1 == 'E' and data->2 == 'T' and data->3 == ' '
            len = len - 1
            if len > 64
                len = 64 // maximum ProDOS path
            fin
            for i = 4 to len // get ProDOS path from URL
                if data->[i] <= ' '
                    data->3 = i - 4
                    url = data + 3
                    if url->1 == '/'
                        if url->0 == 1
                            url = @defhtml // Is this a directory with no file given? Use index.html
                        else
                            url->1 = url->0 - 1
                            url = url + 1
                        fin
                    fin
                    strcat2(@filename, @prefix, url)
                    puts("GET:"); puts(@filename);putln
                    //
                    // Get file info
                    //
                    //puts("getting file info "); // debug
                    refnum = fileio:open(@filename) // try to open this file with ProDOS
                    if refnum // file was opened OK
                        filelen = fileio:geteof(refnum) // get length of file for Content-Length
                        lenstr = itos(@lenstr + 1, filelen) - (@lenstr + 1)
                        strcat2(@okhdr, @httpOK, @httpContentLen)
                        strcat2(@okhdr, @okhdr, @lenstr)
                        strcat2(@okhdr, @okhdr, "\n\r")
                        //
                        // Content type header
                        //
                        fileio:getfileinfo(@filename, @fileInfo)
                        if fileInfo.1 == $03 OR fileInfo.1 == $04
                            //
                            // this a text file
                            //
                            //puts(@mimeTextHtml) // debug
                            strcat2(@okhdr, @okhdr, @httpContentType)
                            strcat2(@okhdr, @okhdr, @mimeTextHtml)
                        else
                            //
                            // send as binary attachment
                            //
                            //puts(@mimeOctetStream) // debug
                            strcat2(@okhdr, @okhdr, @httpContentType)
                            strcat2(@okhdr, @okhdr, @mimeOctetStream)
                            strcat2(@okhdr, @okhdr, "\n\r")
                            //
                            // and send filename too
                            //
                            strcat2(@okhdr, @okhdr, @httpContentAttach)
                            // todo: get the base filename...
                        fin
                        strcat2(@okhdr, @okhdr, @httpEnd)
                        //dumpchars(@okhdr + 1, okhdr) // debug
                        iNet:sendTCP(socketHTTP, @okhdr + 1, okhdr) // send HTTP response header to client
                        sendFile(refnum, socketHTTP, filelen) // send file data to client
                        fileio:close(refnum)
                    else // file couldn't be opened, so return 404 on this
                        puts("404 Not Found");putln // debug
                        iNet:sendTCP(socketHTTP, @httpNOTFOUND + 1, httpNOTFOUND)
                    fin // refnum
                    break
                fin
            next
        else
            iNet:sendTCP(socketHTTP, @httpBAD + 1, httpBAD)
        fin
    fin
    socketHTTP = iNet:closeTCP(socketHTTP)
    return 0
end

if !iNet:initIP()
    return -1
fin
puts("PLASMA Web Server, Version 1.0\n")
fileio:getpfx(@prefix)
//
// Alloc aligned file/io buffers
//
filebuff = heapallocalign(1024, 8, 0)
//
// Service IP
//
repeat
    //
    // (Re)Open HTTPD port
    //
    if !socketHTTP
        socketHTTP = iNet:listenTCP(80, @servHTTP, 0)
    fin
    iNet:serviceIP()
until conio:keypressed()

done
