⌈⌋ ⎇ branch:  freshcode


Check-in [3ea5b33630]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:initial API handler (just GET/query project info as of now)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 3ea5b33630e0fa36851ff91b0038adcfedfc34b2
User & Date: mario 2014-07-28 17:53:59
Context
2014-07-29
04:22
Add ::scope_tags() and ::state_tag() extraction helper check-in: c2179a52df user: mario tags: trunk
2014-07-28
17:53
initial API handler (just GET/query project info as of now) check-in: 3ea5b33630 user: mario tags: trunk
17:52
Add colorization to trove list check-in: 1d51babc9c user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to .htaccess.

17
18
19
20
21
22
23
24














25
26
27
28
29
30
31
32
33
34
35
36
RewriteCond  %{REQUEST_METHOD}  ^GET$
RewriteCond  %{HTTP_HOST}       ^ww+\.(\w+\.\w+)\.?$
RewriteRule  ^(.*)$             http://%1/$1            [R,QSA,L]

#-- RSS/Atom aliases
RewriteCond  %{QUERY_STRING}    ^format=(atom|rss)$
RewriteRule  ^$ feed/xfer.%1
RewriteRule ^(?:projects)\.(atom|rss|json)$  feed/xfer.$1















#-- Page dispatching
RewriteRule  ^$                 index.php?page=index    [L,NS,QSA]
RewriteRule  ^(projects|submit|search|flag|tags?|feed|login|links|forum|admin)\b/?(\w+(?:[-_]\w+)*)?(?:\.(json|atom|rss))?/?$   index.php?page=$1&name=$2&ext=$3   [L,NS,QSA]

#-- Deny direct invocations
RewriteRule  ^freshcode\.db.*$  -                       [F]
RewriteRule  ^\.                -                       [F]
RewriteCond  %{ENV:REDIRECT_STATUS}  !200
RewriteRule  ^\w+\.php(|/.*)$   -                       [F,L,NS]









|
>
>
>
>
>
>
>
>
>
>
>
>
>
>












17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
RewriteCond  %{REQUEST_METHOD}  ^GET$
RewriteCond  %{HTTP_HOST}       ^ww+\.(\w+\.\w+)\.?$
RewriteRule  ^(.*)$             http://%1/$1            [R,QSA,L]

#-- RSS/Atom aliases
RewriteCond  %{QUERY_STRING}    ^format=(atom|rss)$
RewriteRule  ^$ feed/xfer.%1
RewriteRule  ^(?:projects)\.(atom|rss|json)$  feed/xfer.$1

#-- Freecode API mapping
RewriteCond  %{QUERY_STRING}    auth_code
RewriteCond  %{REQUEST_METHOD}  ^GET$
RewriteRule  ^projects/([\w-_]+)\.json$  index.php?page=api&name=$1&api=query [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^PUT$
RewriteRule  ^projects/([\w-_]+)\.json$  index.php?page=api&name=$1&api=publish [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^POST$
RewriteRule  ^projects/([\w-_]+)/releases\.json$  index.php?page=api&name=$1&api=update_core [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^(GET|DELETE)$
RewriteRule  ^projects/([\w-_]+)/releases/(\w+)\.json$  index.php?page=api&name=$1&api=version_%1 [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^(PUT|POST|DELETE)$
RewriteRule  ^projects/([\w-_]+)/urls(?:/([\w-/._\s]+))?\.json$  index.php?page=api&name=$1&api=urls&label=$2 [L,NS,QSA] 


#-- Page dispatching
RewriteRule  ^$                 index.php?page=index    [L,NS,QSA]
RewriteRule  ^(projects|submit|search|flag|tags?|feed|login|links|forum|admin)\b/?(\w+(?:[-_]\w+)*)?(?:\.(json|atom|rss))?/?$   index.php?page=$1&name=$2&ext=$3   [L,NS,QSA]

#-- Deny direct invocations
RewriteRule  ^freshcode\.db.*$  -                       [F]
RewriteRule  ^\.                -                       [F]
RewriteCond  %{ENV:REDIRECT_STATUS}  !200
RewriteRule  ^\w+\.php(|/.*)$   -                       [F,L,NS]


Added handler_api.php.















































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<?php
/**
 * api: php
 * title: Submit API
 * description: Implements the Freecode JSON Rest API for release updates
 * version: 0.1
 * author: mario
 * license: AGPL
 * 
 * RewriteRules dispatch following Freecode API request paths:
 *
 *     GET   projects/<name>.json                query
 *     PUT   projects/<name>.json                update_core
 *    POST   projects/<name>/releases.json       publish
 *     GET   projects/<name>/releases/<w>.json   version_GET
 *  DELETE   projects/<name>/releases/<i>.json   version_DELETE
 *     PUT   projects/<name>/urls/<id>.json      urls, label=id
 *    POST   projects/<name>/urls.json           urls
 *  DELETE   projects/<name>/urls/<id>.json      urls, label=id
 *
 *
 * At this point everything went through index.php already, so environment
 * is initialized. Therefore API methods can be invoked directly, which
 * either retrieve or store project data, and prepare a JSON response.
 *
 */


// Wraps API methods and utility code
class FreeCode_API {


    /**
     * Initialize params from RewriteRule args
     *
     */
    function __construct() {
    
        // URL params
        $this->name = $_GET->proj_name["name"];
        $this->api = $_GET->id->default…error["api"];
        $this->method = $_SERVER->id["REQUEST_METHOD"];
        $this->auth_code = $_REQUEST->text["auth_code"];
        
        // Request body
        $this->body = new input(
            $_SERVER->int["CONTENT_LENGTH"] && $_SERVER->stristr…json->is_int["CONTENT_TYPE"]
            ? json_decode(file_get_contents("php://input"), TRUE)
            : array()
        );
        
        // Might package its own auth token
        if ($this->body->has("auth_code")) {
            $this->auth_code = $this->body->text["auth_code"];
        }

        file_put_contents("api-acc", json_encode($_SERVER->__vars, JSON_PRETTY_PRINT));
    }

    
    /**
     * Invoke API target function after retrieving project data.
     *
     */
    function dispatch() {
        if (!$project = new API_release($this->name)) {
            $this->error("404 No such project", "Invalid Project ID");
        }
        $this->json_exit(
            $this->{$this->api}($project)
        );
    }


    /**
     * GET project description.
     *
     */
    function query($project) {
        return $this->project_wrap($this->auth_filter($project));
    }
    
    // Alias some fields for fc-submit, but append our data scheme intact
    function project_wrap($data) {
        return array(
            "project" => array(
                 "id" => crc32($data["name"]),
                 "permalink" => $data["name"],
                 "oneliner" => substr($data["description"], 0, 100),
                 "license_list" => p_csv($data["license"]),
                 "tag_list" => p_csv($data["tags"]),
                 "approved_urls" => $this->urls(p_key_value($data["urls"]))
            ) + $data->getArrayCopy()
        );
    }
    
    // Expand associative URLs into [{label:,redirector:},..] list
    function urls($kv, $r=array()) {
        foreach ($kv as $key=>$value) {
            $r[] = array("label"=>$key, "redirector"=>$value);
        }
        return $r;
    }



    
    
    /**
     * Strip down raw project data for absent auth_code
     * in read/GET requests.
     *
     */
    function auth_filter($data) {
        if (!$this->is_authorized($data)) {
            unset(
                $data["lock"], $data["submitter_openid"], $data["submitter"],
                $data["hidden"], $data["deleted"], $data["flag"],
                $data["social_links"]
            );
        }
        return $data;
    }

    
    /**
     * Prevent further operations for (write) requests that
     * actually REQUIRE a valid authorization token.
     *
     */
    function with_permission($data) {
        return $this->is_authorized($data)
             ? $data
             : $this->error("401 Unauthorized", "API password hash does not match. Add a crypt(3) password in your freshcode.club project entries `lock` field, comma-delimited to your OpenID handle.");
    }

    /**
     * The `lock` field usually contains one or more OpenID urls. It's
     * a comma-delimited field.
     *
     * Using the API additionally requires a password hash, as in crypt(3)
     * or `openssl passwd -1` or PHPs password_hash(), to be present.
     *
     * It will simply be compared against the ?auth_code= parameter.
     *
     */
    function is_authorized($data) {
        foreach (preg_grep("/\$/", p_csv($data["lock"])) as $hash) {
            if (password_verify($this->auth_code, $hash)) {
                return TRUE;
            }
        }
        return FALSE;
    }


    /**
     * JSON encode and finish.
     *
     */
    function json_exit($data) {
        header("Content-Type2: json/vnd.freecode.com; version=3; charset=UTF-8");
        header("Content-Type: application/json");
        exit(
            json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        );
    }


    /**
     * Bail with error response.
     *
     */
    function error($http = "503 Unavailable", $json = "unknown method") {
        header("Status: $http");
        $this->json_exit(["error" => "$json"]);
    }

}



/**
 * Map field identifiers
 *
 */
class API_release extends release {
    function __construct($name) {
        parent::__construct($name);
        $this["id"] = $this["name"];
        $this["permalink"] = $this["name"];
    }
}





?>

Changes to index.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/**
 * api: php
 * title: Freshcode.club
 * description: FLOSS software release tracking website
 * version: 0.5.2
 * author: mario
 * license: AGPL
 * 
 * Implements a freshmeat/freecode-like directory for open source
 * release publishing / tracking.
 *
 */





|







1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/**
 * api: php
 * title: Freshcode.club
 * description: FLOSS software release tracking website
 * version: 0.5.3
 * author: mario
 * license: AGPL
 * 
 * Implements a freshmeat/freecode-like directory for open source
 * release publishing / tracking.
 *
 */
34
35
36
37
38
39
40





41
42
43
44
45
46
47
    case "flag":
    case "submit":
        if ((LOGIN_REQUIRED or $page === "flag") and empty($_SESSION["openid"])) {
            exit(include("page_login.php"));
        }
        include("page_$page.php");
        break;






    case "admin":
        if (!in_array($_SESSION["openid"], $moderator_ids)) {
            exit(include("page_login.php"));
        }
        include("page_admin.php");
        break;







>
>
>
>
>







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    case "flag":
    case "submit":
        if ((LOGIN_REQUIRED or $page === "flag") and empty($_SESSION["openid"])) {
            exit(include("page_login.php"));
        }
        include("page_$page.php");
        break;

    case "api":
        $api = new FreeCode_API();
        $api->dispatch();
        break;

    case "admin":
        if (!in_array($_SESSION["openid"], $moderator_ids)) {
            exit(include("page_login.php"));
        }
        include("page_admin.php");
        break;