Login
cgi-init.th1ish at [94e51dc289]
Login

File th1ish/cgi-init.th1ish artifact 75a2820be1 part of check-in 94e51dc289


/**

*/

api.import('unit-common')
api.cgi || api.loadModule('cgiish')
assert api.cgi
assert Fossil

Fossil.client = object{}


/**
  If passed no args, returns this instance's private cache
  storage (an Object). If passed 1 arg, returns the value
  of cache.(argv.0). Otherwise is stores the value of argv.1
  into the cache using the key argv.0 and returns the
  value it cached.
*/
Fossil.Context.cache = proc(){
    const c = (this._cache || (this._cache = object{}))
    const argc = argv.length()
    argc || return c
    (1==argc) && return c.(argv.0)
    return c.(argv.0) = argv.1
}

Fossil.Context.getProjectName = proc(){
    var rc = this.cache('project-name')
    if(!rc){
      rc = this.selectVal({
           SELECT value FROM config WHERE name='project-name'
      })
      rc && this.cache('project-name', rc)
    }
    return rc
}

catch { /* Set up some extra environment... */
    const R = api.cgi.request;
    const uri = R.ENV.REQUEST_URI
    uri && (api.cgi.cgiRoot = uri.split('.cgi').0+'.cgi/')
    const serverName = R.ENV.SERVER_NAME
    api.cgi.isLocalServer = (!) || (0<=serverName.indexOf(".local"))
}

api.cgi.config = object{
    scrubExceptions: !api.cgi.isLocalServer,//true,
    onlyDeepestException: true,//false
    sessionCookieKey: 'fSession',
    cookiePath: api.cgi.cgiRoot
}

/**
    Removes "potentially security-relevant" properties from the
    exception ex, recursively in all 'rethrown' exceptions. If
    ex.rethrown is set and api.cgi.config.onlyDeepestException is
    true, it returns that value, else return ex.
*/
api.cgi.scrubException = proc(ex){
    if(this.config.scrubExceptions){
        unset ex.script, ex.callStack //, ex.line, ex.column // security-relevant
    }
    //this.config.onlyDeepestException && return ex
    ex.rethrown && argv.callee.call(this, ex.rethrown)
    return (this.config.onlyDeepestException && ex.rethrown) ? ex.rethrown : ex
}

/**
  Assumes obj is a legal value for the 'J' format specifier for Buffer.appendf()
  and outputs that data using some default, unspecified JSON indentation setting.
*/
api.cgi.printJSON = proc(obj){
    print("%1$2J".applyFormat(obj))
}

/**
  Assumes obj is a legal value for the 'J' format specifier for Buffer.appendf()
  and returns that data's JSON string form, unspecified JSON indentation setting.
*/
api.cgi.toJSONString = proc(obj){
    return "%1$2J".applyFormat(obj)
}

/**
  Returns a variable from the GET, POST, or COOKIES
  data, in that order, or default if none of those
  sets contain the given key.
*/
api.cgi.getVar = proc(name, default = undefined){
    const R = this.request
    var rc = default
    if(R.GET && R.GET.hasOwnProperty(name)){
        rc = R.GET.(name)
        //CGI.setCookie('limit', resultLimit)
    }else if(R.POST && R.POST.hasOwnProperty(name)){
        rc = R.POST.(name)
    }else if(R.COOKIES && R.COOKIES.hasOwnProperty(name)){
        rc = R.COOKIES.(name)
    }
    return rc
}

api.cgi.getCookie = proc(name){
    const c = this.request.COOKIES
    return (c && c.hasOwnProperty(name)) ? c.(name) : undefined
}

/**
  Returns an app-specific session object, for storing _small amounts_
  of persistent data. The session is submitted as a single JSON-encoded
  cookie, so the data should be small.

  The session is loaded or initialized the first time this is called,
  and each time it is called, the session's 'lifetime' property is
  updated.
*/
api.cgi.getSession = proc(){
    var s = argv.callee.session
    if(!s){
        s = this.getCookie(this.config.sessionCookieKey)
        s && catch{
            const x = s
            s = null
            s = api.json.parse(x)
        }
        s || (s = object{
            sessionId: 1234,
            startTime: time()
        })
        s.startTime || (s.startTime = time())
        s.lifetime = time()-s.startTime
        s.visitCount = 1 + (+s.visitCount)
        argv.callee.session = s
    }
    return s
}

/**
  Don't use this - it turns out that each sub-path sets its own
  cookie, no matter what path we tell it to use.

  Encodes api.cgi.getSession() to JSON and sets it as the
  cookie named by api.cgi.config.sessionCookieKey.
*/
api.cgi.savePathSession = proc(){
    var s = this.getSession()
    this.setCookie(this.config.sessionCookieKey, object{
      path: '/', // this.config.cookiePath,
      value:"%1$J".applyFormat(s)
    })
}


/**
    Expects func() to be a page generation function. To return
    JSON, the function must simply return an Object or Array.
    To generate other output, the function must call
    api.cgi.setContentType(), use api.io.output() (or its
    convenience form print(), or similar) to generate output,
    and must return a falsy value.

    func() is passed the CGI module's "this" as its parameter.

    exit()s on success.
*/
api.cgi.runFuncAsPage = proc(func){
    api.ob.push()
    var saveSession = false
    var rc = catch{
        assert 'function' === typename func
        assert func inherits api.prototypes.Function
        toss func(this)
    }
    if(rc inherits api.Exception){
        saveSession = false
        rc = object{
            exception: this.scrubException(rc)
        }
    }else if(rc){ 
        rc = object{
            body: rc
        }
    }
    if(rc){
        api.ob.clear()
        this.setContentType('application/json')
        this.printJSON(rc)
    }
    saveSession && this.savePathSession()
    exit api.cgi.send()
}

api.cgi.util = object {
    assertHasRepo: proc(){
        assert Fossil.cx
        assert Fossil.cx.db
        assert Fossil.cx.db.repo
        return Fossil.cx.db.repo
    },
    assertHasCheckout: proc(){
        assert Fossil.cx
        assert Fossil.cx.db
        assert Fossil.cx.db.checkout
        return Fossil.cx.db.checkout
    }
}

/**
    Dispatches to one of api.cgi.pages' pages, depending on
    the PATH_INFO. It either throws or exit()s, but never
    returns successfully.
*/
api.cgi.dispatchPage = proc(){
    const CGI = api.cgi
    const R = CGI.request

    this.util.assertHasRepo()

    // Look for a CGI.pages entry matching PATH_INFO...
    var ps = R.ENV.PATH_INFO_SPLIT
    var next = CGI.pages
    var rightMost = next
    var leftMost = array[]
    // th1ish bug: cloning of arrays returns a Function:
    // var rightPath = ps ? clone ps : undefined
     var rightPath = ps ? ps.slice()/*clone*/ : undefined
    //print(typename ps, ps)
    ps && for{var x, i = 0, key, psLen = [ps.length]}
        {next && (i<psLen)}{i+=1}{
        key = ps.(i)
        next.hasOwnProperty(key) || continue
        x = next.(key)
        next = x || undefined
        next && (rightMost = next)
        leftMost.push(rightPath.shift())
    }
    var cb
    if ('function' === typename rightMost){
        cb = rightMost
    }else{
        if(ps && ps.length()){
            throw api.Exception(Fossil.rc.NOT_FOUND, "Unknown page: "+R.ENV.PATH_INFO)
        }else{
            cb = CGI.pages._default
        }
    }
    //print('cb',cb)
    //R.ENV.rightPath = rightPath.join('/')
    //R.ENV.leftPath = leftMost.join('/')
    CGI.runFuncAsPage(cb)
    throw __SRC+": cannot be reached"
}

api.cgi.createPageProxyForFile = proc(pageBaseName){

    const f = proc(cgi){
        const pf = api.import.path
        const pageName = {pages/}+pageBaseName
        const fn = pf.search(pageName)
        fn || throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find page "+pageName)
        //print(pf.prefix, pf.suffix)
        import(fn)
    }.importSymbols('pageBaseName')
    return f
}

api.cgi.pages = object {
    foo: object{
        bar:proc(cgi){
            cgi.setContentType('text/plain')
            print("should not be emitted")
            api.ob.clear()
            print(/*__SRC,*/"hi from", cgi.request.ENV.PATH_INFO)
        }
    },
    err1: 'not a function',
    cookie: proc(cgi){
        cgi.setHeader('X-request-time', [time]);
        cgi.setCookie('myCookie', true);
        cgi.setCookie('yourCookie', object{
            path: '/'
            // domain: string,
            httpOnly: true,
            // secure: bool,
            value:'1 2 3',
            expires: [time] + (60*60*24)
        });
        cgi.setContentType('text/html')
        assert 0 < [api.ob.level]
        $print {<html><body><h1>Hi from cgiish!</h1>}
        const R = cgi.request
        $print {<textarea cols='80' rows='20'>} \
            object {
                timestamp: [time],
                cookies: R.COOKIES,
                //env: R.ENV,
                get: R.GET
            } \
            {</textarea>}
        $print {</body></html>}
    },
    manifest: api.cgi.createPageProxyForFile('manifest'),
    snap: proc(cgi){
        //const x = api.prototypes.Exception(3,"!!!");
        //throw x ; //"Aw, snap! Google changed its APIs again!";
        throw api.Exception(Fossil.rc.ERROR, "Aw, snap! Google v8 changed its APIs again!")
    },
    timeline: scope{
        var pmain = api.cgi.createPageProxyForFile('timeline')
        pmain.html = api.cgi.createPageProxyForFile('timeline-html')
        pmain
    }/*object{
        text: api.cgi.createPageProxyForFile('timeline'),
        html: api.cgi.createPageProxyForFile('timeline-html')
    }*/,
    _default: proc(cgi){
        const R = cgi.request
        const now = time()
        //cgi.setCookie( 'default-page', object{value: now, path:'/foo/', httpOnly:true})
        var rc = object {
            timestamp: [time],
            //cookies: R.COOKIES,
            get: R.GET,
            //ARGV: ARGV
            cgiRoot: cgi.cgiRoot,
            //session: cgi.getSession(),
            'Fossil.rc': Fossil.rc
        }
        if(cgi.isLocalServer){
            rc.privileged = object{
                env: R.ENV
            }
        }

        if(Fossil.cx && Fossil.cx.db && Fossil.cx.db.repo){
            rc.context = "%1$p".applyFormat(Fossil.cx)
            rc.repo = object{
                trunkVersion: Fossil.cx.symToUuid("trunk"),
                projectName: Fossil.cx.getProjectName()
            }
        }
        return rc
    }
}

unset api.cgi.createPageProxyForFile