Login
init.s2 at [428464d569]
Login

File s2/fslcgi.d/init.s2 artifact 3ef83e6eb7 part of check-in 428464d569


/**
   Post-bootstrap initialization code for s2's fslcgi. Must live in the
   root of "resource directory", but that (with sufficient hacking) may
   live outside of the web-root.
*/
affirm 'undefined' !== typename $CGI /* expecting CGI module to be loaded under this name */;
affirm Fossil.require;

$CGI.config = {
    resourceDir: Fossil.file.canonicalName(Fossil.file.dirPart(__FILE, true))    
};
scope {
    const C = $CGI;
    var uri = s2.getenv('SCRIPT_NAME');
    // Set up cgiRoot (the "standard" way of getting the root dir
    // for link-building purposes). This kludgery is not generic...
    if(uri) { C.config.cgiRoot = uri + '/' }
    else{
        uri = s2.getenv('REQUEST_URI');
        C.request.ENV || (C.request.REQUEST_URI = uri);
        if(uri){
            var adj = uri.split('?').0.split('fslcgi',2);
            C.config.cgiRoot = adj.0.concat('fslcgi', '/');
        }
    }
    
    C.config.localServerMode =
        (var serverName = s2.getenv('SERVER_NAME'))
        && (0<serverName.indexOf(".local"));
    ;;
}

/**
   A convenience alias for s2.io.output (or functionally
   equivalent).
*/
$CGI.out = s2.io.output;

/**
   Sends its RHS argument output to the (buffered) response body.
   Returns this object (so it can be chained).
*/
$CGI.'operator<<' = proc(self,arg){
    return s2.io.output(arg), this;
};

/**
    Removes "potentially security-relevant" properties from the
    exception ex. Returns ex.
*/
$CGI.scrubException = proc(ex){
    if(this.config.scrubExceptions){
        unset ex.script, ex.stackTrace, ex.line, ex.column // security-relevant
    }
    return ex;
};

$CGI.util = {
    /**
       Converts all key/value pairs from obj to a string of urlencoded
       key value pairs, separated by '&'. If addQMark is true then the
       result is prefixed by a '?', otherwise it is not.

       If obj has no properties then an empty string is returned,
       regardless of addQMark.
    */
    objToUrlOpt: proc(obj, addQMark = false){
        affirm obj && obj.eachProperty /* expecting an Object */;
        buf.reset();
        obj.eachProperty(each);
        return buf.length()
            ? addQMark
              ? '?' + buf.toString()
              : buf.toString()
            : '';
    }.importSymbols({
        buf: s2.Buffer.new(),
        each: proc(k,v){
            buf.length() && buf.append('&');
            buf.append($CGI.urlencode(''+k),'=',$CGI.urlencode(''+v));
            /* Reminder to self: $CGI.urlencode() and friends require
               that $CGI be their 'this' because they reuse an internal
               buffer on each call to save on allocations.
            */
        }
    }),

    absoluteLink: proc(path,label=path, urlOpt){
        affirm 'string' === typename path;
        const hasQ = path.indexOf('?')>=0;
        var optStr = this.objToUrlOpt(urlOpt, !hasQ );
        return fmt.applyFormat(
            $CGI.config.cgiRoot,
            path+(optStr ? hasQ ? '&' + optStr : optStr : ''),
            label);
    }.importSymbols({
        fmt: "<a href='%1$s%2$s'>%3$s</a>"
    });
};


$CGI.resourcePath = proc(name){
    return this.config.resourceDir.concat(name);
};

$CGI.getCLIFlag = proc(k, default){
    return (s2.ARGV && s2.ARGV.flags)
        ? s2.ARGV.flags.hasOwnProperty(k) ? s2.ARGV.flags[k] : default
        : default;
};

$CGI.getFossilInstance = proc(){
    //throw "Don't call this - use require() instead.";
    var f = Fossil.require.fsl  /* internal details */;
    affirm f inherits Fossil.Context;
    if(!f.db){
        var repoDb = this.getCLIFlag('repo-db');
        repoDb || throw "Pass --repo-db=/path/to/repo.db in the "
            + "SCRIPT flags (after --) to the main CGI script.";
        f = Fossil.Context.new().openRepo(repoDb);
        //print("Opened: ",callee.f.db.filename);
    }
    return f;
};

// incomplete... not yet sure what i want.
$CGI.request.pathInfoList = scope {
    var ps, p = s2.getenv('PATH_INFO');
    if(p && '/'!==p){
        ps = p.split('/');
        // Trim leading/trailing null entries caused
        // by leading/trailing slashes:
        ps.0 || ps.shift();
        ps[ps.length()-1] || ps.pop();
    }
    ps;
};

if(1){ // only for testing
    print($CGI.request.toJSONString(2));
}else if(0){
    const c = $CGI;
    c.setContentType('text/plain');
    print("$CGI sanity checks...");
    c << 'Fossil.time.runTimeMs() says: '
        << Fossil.time.runTimeMs()
        << '\nCLI flags:\n'
        << (s2.ARGV ? s2.ARGV.toJSONString(2) : 'none')
        << '\n'
    ;

    const f = c.getFossilInstance();
    f.db || f.openCheckout();
    c << 'Fossil context: ' << f << ' repo db: ' << f.db.filename << '\n';
    c << "pathInfoList = "<<c.request.pathInfoList<<'\n';
    c << "resourcePath(foo/bar.baz)==>"
        << c.resourcePath('foo/bar.baz')
        << '\n';

    c << "Most recent timeline event: "
        << f.db.selectRow('SELECT * FROM event ORDER BY mtime DESC')
        .toJSONString(2)
        << '\n';

    if(0){
        // Workaround: c is a Native, and those are rejected
        // by the C-level toJSON bits:
        var obj = {};
        c.eachProperty(proc(k,v){obj[k]=v});
        c << '$CGI properties:\n' << obj.toJSONString(4) << '\n';
    }
    if(0){
        c << 'objToUrlOpt: '
            << c.util.objToUrlOpt({a:3,c:'a b'})
            << '\n';
        c << 'objToUrlOpt again (should be empty): '
            << c.util.objToUrlOpt({},true)
            << '\n';
        c << 'absoluteLink: '
            << c.util.absoluteLink('foo/bar/baz?x=y', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'absoluteLink again: '
            << c.util.absoluteLink('foo/bar/', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'Fossil.artifactTypes = '<<Fossil.artifactTypes<<'\n';
        
    }

    c << '\nThe end. Fossil.time.runTimeMs() says: '
        << Fossil.time.runTimeMs()
        << '\n';

    //throw "testing discarding of buffered output";
}