k2hr3-api 1.0.42 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/k2hr3-init.sh.templ +2 -2
- package/dist/.gitkeep +0 -0
- package/dist/src/app.js +262 -0
- package/{bin → dist/src/bin}/run.sh +1 -1
- package/dist/src/bin/watcher.js +113 -0
- package/dist/src/bin/www.js +217 -0
- package/dist/src/lib/basicipcheck.js +392 -0
- package/dist/src/lib/cacerts.js +106 -0
- package/dist/src/lib/dbglogging.js +190 -0
- package/dist/src/lib/dummyuserapi.js +719 -0
- package/dist/src/lib/ipwatch.js +354 -0
- package/dist/src/lib/k2hr3acrutil.js +532 -0
- package/dist/src/lib/k2hr3apiutil.js +1444 -0
- package/dist/src/lib/k2hr3cliutil.js +183 -0
- package/dist/src/lib/k2hr3config.js +832 -0
- package/dist/src/lib/k2hr3cryptutil.js +258 -0
- package/dist/src/lib/k2hr3dkc.js +12121 -0
- package/dist/src/lib/k2hr3extdata.js +198 -0
- package/dist/src/lib/k2hr3keys.js +207 -0
- package/dist/src/lib/k2hr3resutil.js +111 -0
- package/dist/src/lib/k2hr3template.js +6546 -0
- package/dist/src/lib/k2hr3tokens.js +2643 -0
- package/dist/src/lib/k2hr3userdata.js +296 -0
- package/dist/src/lib/k8soidc.js +1000 -0
- package/dist/src/lib/openstackapiv2.js +695 -0
- package/dist/src/lib/openstackapiv3.js +932 -0
- package/dist/src/lib/openstackep.js +667 -0
- package/{tests/auto_common.js → dist/src/lib/types.js} +4 -38
- package/dist/src/routes/acr.js +704 -0
- package/dist/src/routes/debugVerify.js +294 -0
- package/dist/src/routes/extdata.js +219 -0
- package/dist/src/routes/list.js +264 -0
- package/dist/src/routes/policy.js +840 -0
- package/dist/src/routes/resource.js +1489 -0
- package/dist/src/routes/role.js +2627 -0
- package/dist/src/routes/service.js +908 -0
- package/dist/src/routes/tenant.js +1141 -0
- package/dist/src/routes/userTokens.js +482 -0
- package/dist/src/routes/userdata.js +212 -0
- package/dist/src/routes/version.js +103 -0
- package/package.json +152 -121
- package/ChangeLog +0 -378
- package/app.js +0 -292
- package/bin/watcher +0 -122
- package/bin/www +0 -180
- package/eslint.config.mjs +0 -68
- package/lib/basicipcheck.js +0 -376
- package/lib/cacerts.js +0 -71
- package/lib/dbglogging.js +0 -151
- package/lib/dummyuserapi.js +0 -766
- package/lib/ipwatch.js +0 -379
- package/lib/k2hr3acrutil.js +0 -516
- package/lib/k2hr3apiutil.js +0 -1494
- package/lib/k2hr3cliutil.js +0 -191
- package/lib/k2hr3config.js +0 -826
- package/lib/k2hr3cryptutil.js +0 -254
- package/lib/k2hr3dkc.js +0 -12632
- package/lib/k2hr3extdata.js +0 -198
- package/lib/k2hr3keys.js +0 -234
- package/lib/k2hr3resutil.js +0 -100
- package/lib/k2hr3template.js +0 -6925
- package/lib/k2hr3tokens.js +0 -2799
- package/lib/k2hr3userdata.js +0 -312
- package/lib/k8soidc.js +0 -1012
- package/lib/openstackapiv2.js +0 -764
- package/lib/openstackapiv3.js +0 -1032
- package/lib/openstackep.js +0 -553
- package/routes/acr.js +0 -738
- package/routes/debugVerify.js +0 -263
- package/routes/extdata.js +0 -232
- package/routes/list.js +0 -270
- package/routes/policy.js +0 -869
- package/routes/resource.js +0 -1441
- package/routes/role.js +0 -2664
- package/routes/service.js +0 -894
- package/routes/tenant.js +0 -1095
- package/routes/userTokens.js +0 -511
- package/routes/userdata.js +0 -218
- package/routes/version.js +0 -108
- package/templ/Dockerfile.templ +0 -71
- package/tests/auto_acr.js +0 -1101
- package/tests/auto_acr_spec.js +0 -79
- package/tests/auto_all_spec.js +0 -142
- package/tests/auto_control_subprocess.sh +0 -243
- package/tests/auto_extdata.js +0 -220
- package/tests/auto_extdata_spec.js +0 -79
- package/tests/auto_init_config_json.sh +0 -275
- package/tests/auto_k2hdkc_server.ini +0 -109
- package/tests/auto_k2hdkc_slave.ini +0 -83
- package/tests/auto_list.js +0 -439
- package/tests/auto_list_spec.js +0 -79
- package/tests/auto_policy.js +0 -1579
- package/tests/auto_policy_spec.js +0 -79
- package/tests/auto_resource.js +0 -10956
- package/tests/auto_resource_spec.js +0 -79
- package/tests/auto_role.js +0 -6150
- package/tests/auto_role_spec.js +0 -79
- package/tests/auto_service.js +0 -770
- package/tests/auto_service_spec.js +0 -79
- package/tests/auto_subprocesses.js +0 -114
- package/tests/auto_template.sh +0 -126
- package/tests/auto_tenant.js +0 -1100
- package/tests/auto_tenant_spec.js +0 -79
- package/tests/auto_token_util.js +0 -219
- package/tests/auto_userdata.js +0 -292
- package/tests/auto_userdata_spec.js +0 -79
- package/tests/auto_usertokens.js +0 -565
- package/tests/auto_usertokens_spec.js +0 -79
- package/tests/auto_version.js +0 -127
- package/tests/auto_version_spec.js +0 -79
- package/tests/auto_watcher.js +0 -157
- package/tests/auto_watcher_spec.js +0 -79
- package/tests/k2hdkc_test.data +0 -986
- package/tests/k2hdkc_test_load.sh +0 -255
- package/tests/k2hr3template_test.js +0 -187
- package/tests/k2hr3template_test.sh +0 -339
- package/tests/k2hr3template_test_async.js +0 -216
- package/tests/k2hr3template_test_template.result +0 -7117
- package/tests/k2hr3template_test_template.txt +0 -3608
- package/tests/k2hr3template_test_vars.js +0 -194
- package/tests/manual_acr_delete.js +0 -143
- package/tests/manual_acr_get.js +0 -297
- package/tests/manual_acr_postput.js +0 -215
- package/tests/manual_allusertenant_get.js +0 -113
- package/tests/manual_extdata_get.js +0 -191
- package/tests/manual_k2hr3keys_get.js +0 -84
- package/tests/manual_list_gethead.js +0 -230
- package/tests/manual_policy_delete.js +0 -132
- package/tests/manual_policy_gethead.js +0 -275
- package/tests/manual_policy_postput.js +0 -297
- package/tests/manual_resource_delete.js +0 -433
- package/tests/manual_resource_gethead.js +0 -423
- package/tests/manual_resource_postput.js +0 -487
- package/tests/manual_role_delete.js +0 -404
- package/tests/manual_role_gethead.js +0 -547
- package/tests/manual_role_postput.js +0 -544
- package/tests/manual_service_delete.js +0 -153
- package/tests/manual_service_gethead.js +0 -178
- package/tests/manual_service_postput.js +0 -348
- package/tests/manual_tenant_delete.js +0 -186
- package/tests/manual_tenant_gethead.js +0 -268
- package/tests/manual_tenant_postput.js +0 -293
- package/tests/manual_test.sh +0 -352
- package/tests/manual_userdata_get.js +0 -173
- package/tests/manual_usertoken_gethead.js +0 -136
- package/tests/manual_usertoken_postput.js +0 -310
- package/tests/manual_version_get.js +0 -127
- package/tests/run_local_test_k2hdkc.sh +0 -174
- package/tests/test.sh +0 -333
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* K2HR3 REST API
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2017 Yahoo Japan Corporation.
|
|
6
|
+
*
|
|
7
|
+
* K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers
|
|
8
|
+
* common management information for the cloud.
|
|
9
|
+
* K2HR3 can dynamically manage information as "who", "what", "operate".
|
|
10
|
+
* These are stored as roles, resources, policies in K2hdkc, and the
|
|
11
|
+
* client system can dynamically read and modify these information.
|
|
12
|
+
*
|
|
13
|
+
* For the full copyright and license information, please view
|
|
14
|
+
* the license file that was distributed with this source code.
|
|
15
|
+
*
|
|
16
|
+
* AUTHOR: Takeshi Nakatani
|
|
17
|
+
* CREATE: Mon Sep 11 2017
|
|
18
|
+
* REVISION:
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
const k2hr3apiutil_1 = __importDefault(require("../lib/k2hr3apiutil"));
|
|
26
|
+
const k2hr3resutil_1 = __importDefault(require("../lib/k2hr3resutil"));
|
|
27
|
+
const k2hr3dkc_1 = __importDefault(require("../lib/k2hr3dkc"));
|
|
28
|
+
const k2hr3tokens_1 = __importDefault(require("../lib/k2hr3tokens"));
|
|
29
|
+
const dbglogging_1 = __importDefault(require("../lib/dbglogging"));
|
|
30
|
+
const express_1 = __importDefault(require("express"));
|
|
31
|
+
const k2hr3keys_1 = require("../lib/k2hr3keys");
|
|
32
|
+
const router = express_1.default.Router();
|
|
33
|
+
//---------------------------------------------------------
|
|
34
|
+
// Router GET/HEAD
|
|
35
|
+
//---------------------------------------------------------
|
|
36
|
+
//
|
|
37
|
+
// Common Utility function
|
|
38
|
+
//
|
|
39
|
+
const rawGetChildrenList = (req, expand) => {
|
|
40
|
+
let result = {
|
|
41
|
+
result: true,
|
|
42
|
+
message: null,
|
|
43
|
+
status: 200
|
|
44
|
+
};
|
|
45
|
+
if (!k2hr3apiutil_1.default.isPlainObject(req) ||
|
|
46
|
+
!k2hr3apiutil_1.default.isSafeString(req.baseUrl)) {
|
|
47
|
+
result.result = false;
|
|
48
|
+
result.message = 'GET/HEAD request or url is wrong';
|
|
49
|
+
result.status = 400; // 400: Bad Request
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
//------------------------------
|
|
53
|
+
// check token
|
|
54
|
+
//------------------------------
|
|
55
|
+
const token_result = k2hr3tokens_1.default.checkToken(req, true, true); // scoped, user token
|
|
56
|
+
if (!token_result.result) {
|
|
57
|
+
result.result = token_result.result;
|
|
58
|
+
result.message = k2hr3apiutil_1.default.getSafeString(token_result.message);
|
|
59
|
+
result.status = k2hr3apiutil_1.default.isSafeNumber(token_result.status) ? token_result.status : 400; // default : 400(Bad Request)
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
const token_info = token_result.token_info;
|
|
63
|
+
if (!k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info)) {
|
|
64
|
+
result.result = false;
|
|
65
|
+
result.message = 'specified wrong token or it is not scoped user token';
|
|
66
|
+
result.status = k2hr3apiutil_1.default.isSafeNumber(token_result.status) ? token_result.status : 400; // default : 400(Bad Request)
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)((k2hr3apiutil_1.default.isString(token_info.user) ? token_info.user : null), (k2hr3apiutil_1.default.isString(token_info.tenant) ? token_info.tenant : null));
|
|
70
|
+
//------------------------------
|
|
71
|
+
// parse uri
|
|
72
|
+
//------------------------------
|
|
73
|
+
const requestptn = new RegExp('^/v1/list/(.*)'); // regex = /^\/v1\/list\/(.*)/
|
|
74
|
+
const reqmatchs = decodeURI(req.baseUrl).match(requestptn);
|
|
75
|
+
if (!k2hr3apiutil_1.default.isArray(reqmatchs) || !k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) {
|
|
76
|
+
result.result = false;
|
|
77
|
+
result.message = 'GET/HEAD request url does not have list type{role, resource, policy}';
|
|
78
|
+
result.status = 400; // 400: Bad Request
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
// parse type & path
|
|
82
|
+
let _pos = reqmatchs[1].indexOf('/');
|
|
83
|
+
let _secondpath = null;
|
|
84
|
+
let _path = null;
|
|
85
|
+
let _service = null;
|
|
86
|
+
let _firstpath;
|
|
87
|
+
let _type;
|
|
88
|
+
if (-1 !== _pos) {
|
|
89
|
+
_firstpath = reqmatchs[1].substr(0, _pos);
|
|
90
|
+
_secondpath = reqmatchs[1].substr(_pos + 1);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
_firstpath = reqmatchs[1];
|
|
94
|
+
}
|
|
95
|
+
// check
|
|
96
|
+
if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_ROLE, _firstpath)) {
|
|
97
|
+
_type = keys.TYPE_ROLE;
|
|
98
|
+
_path = _secondpath;
|
|
99
|
+
}
|
|
100
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_RESOURCE, _firstpath)) {
|
|
101
|
+
_type = keys.TYPE_RESOURCE;
|
|
102
|
+
_path = _secondpath;
|
|
103
|
+
}
|
|
104
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_POLICY, _firstpath)) {
|
|
105
|
+
_type = keys.TYPE_POLICY;
|
|
106
|
+
_path = _secondpath; // should be empty
|
|
107
|
+
}
|
|
108
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_SERVICE, _firstpath)) {
|
|
109
|
+
_type = keys.TYPE_SERVICE;
|
|
110
|
+
}
|
|
111
|
+
else if (null !== _secondpath && k2hr3apiutil_1.default.isSafeString(_secondpath)) {
|
|
112
|
+
// try to check firstpath is service name
|
|
113
|
+
let _thirdpath = null;
|
|
114
|
+
_pos = _secondpath.indexOf('/');
|
|
115
|
+
if (-1 !== _pos) {
|
|
116
|
+
_thirdpath = _secondpath.substr(_pos + 1);
|
|
117
|
+
_secondpath = _secondpath.substr(0, _pos);
|
|
118
|
+
}
|
|
119
|
+
_service = _firstpath.toLowerCase();
|
|
120
|
+
if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_ROLE, _secondpath)) {
|
|
121
|
+
_type = keys.TYPE_ROLE;
|
|
122
|
+
_path = _thirdpath;
|
|
123
|
+
}
|
|
124
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_RESOURCE, _secondpath)) {
|
|
125
|
+
_type = keys.TYPE_RESOURCE;
|
|
126
|
+
_path = _thirdpath;
|
|
127
|
+
}
|
|
128
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.TYPE_POLICY, _secondpath)) {
|
|
129
|
+
_type = keys.TYPE_POLICY;
|
|
130
|
+
_path = _thirdpath; // should be empty
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
result.result = false;
|
|
134
|
+
result.message = 'GET/HEAD request url has wrong list type, it must be "service/role" or "service/resource" or "service/policy"';
|
|
135
|
+
result.status = 400; // 400: Bad Request
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
result.result = false;
|
|
141
|
+
result.message = 'GET/HEAD request url has wrong list type, it must be "role" or "resource" or "policy" or "service"';
|
|
142
|
+
result.status = 400; // 400: Bad Request
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
//------------------------------
|
|
146
|
+
// expand type(only user token type)
|
|
147
|
+
//------------------------------
|
|
148
|
+
let is_expand = false;
|
|
149
|
+
if (k2hr3apiutil_1.default.isBoolean(expand)) {
|
|
150
|
+
// Case for HEAD
|
|
151
|
+
is_expand = expand;
|
|
152
|
+
}
|
|
153
|
+
else if (k2hr3apiutil_1.default.isPlainObject(req.query) && k2hr3apiutil_1.default.isSafeString(req.query.expand)) {
|
|
154
|
+
if (k2hr3apiutil_1.default.compareCaseString(keys.VALUE_TRUE, req.query.expand)) {
|
|
155
|
+
is_expand = true;
|
|
156
|
+
}
|
|
157
|
+
else if (k2hr3apiutil_1.default.compareCaseString(keys.VALUE_FALSE, req.query.expand)) {
|
|
158
|
+
is_expand = false;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
result.result = false;
|
|
162
|
+
result.message = 'GET/HEAD expand url argument parameter(' + JSON.stringify(req.query.expand) + ') is wrong, it must be ' + keys.VALUE_TRUE + ' or ' + keys.VALUE_FALSE + '.';
|
|
163
|
+
result.status = 400; // 400: Bad Request
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//------------------------------
|
|
168
|
+
// get children list
|
|
169
|
+
//------------------------------
|
|
170
|
+
const list_result = k2hr3dkc_1.default.getChildrenList(token_info.tenant, _service, _type, _path, is_expand);
|
|
171
|
+
if (k2hr3dkc_1.default.isResTypeChildrenTree(list_result)) {
|
|
172
|
+
result = list_result;
|
|
173
|
+
if (!result.result) {
|
|
174
|
+
result.message = k2hr3apiutil_1.default.isSafeString(result.message) ? result.message : 'Could not get error message in response from getChildrenList';
|
|
175
|
+
result.status = k2hr3apiutil_1.default.isSafeNumber(result.status) ? result.status : 400; // default : 400(Bad Request)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
result.result = false;
|
|
180
|
+
result.message = 'Could not get response from getChildrenList';
|
|
181
|
+
result.status = 400; // 400: Bad Request
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
};
|
|
185
|
+
//
|
|
186
|
+
// Mountpath : '/v1/list/{role, resource, policy, service}{/...}'
|
|
187
|
+
// : '/v1/list/<service>/{role, resource, policy}{/...}'
|
|
188
|
+
//
|
|
189
|
+
// GET '/v1/list/.../{path}' : get list on version 1
|
|
190
|
+
// HEADER : X-Auth-Token = User token
|
|
191
|
+
// URL arguments : expand = "true"(default) or "false"
|
|
192
|
+
//
|
|
193
|
+
// response body : result => true/false
|
|
194
|
+
// message => error message
|
|
195
|
+
// object => nested objects...
|
|
196
|
+
//
|
|
197
|
+
router.get('/', (req, res, next) => {
|
|
198
|
+
dbglogging_1.default.dlog('CALL:', req.method, req.url);
|
|
199
|
+
if ('GET' !== req.method) {
|
|
200
|
+
// HEAD request comes here, so it should be routed to head function.
|
|
201
|
+
next();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
res.type('application/json; charset=utf-8');
|
|
205
|
+
//
|
|
206
|
+
// get children list
|
|
207
|
+
//
|
|
208
|
+
const result = rawGetChildrenList(req);
|
|
209
|
+
const _message = k2hr3dkc_1.default.isResTypeChildrenTree(result) ? k2hr3apiutil_1.default.getSafeString(result.message) : 'Failed to get list.';
|
|
210
|
+
const _status = (k2hr3dkc_1.default.isResTypeChildrenTree(result) && k2hr3apiutil_1.default.isSafeNumber(result.status)) ? result.status : 500;
|
|
211
|
+
if (!k2hr3dkc_1.default.isResTypeChildrenTree(result) || !result.result) {
|
|
212
|
+
dbglogging_1.default.elog(_message);
|
|
213
|
+
k2hr3resutil_1.default.errResponse(req, res, _status, result);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
dbglogging_1.default.dlog('succeed : ' + _message);
|
|
217
|
+
res.status(200); // 200: OK
|
|
218
|
+
res.send(JSON.stringify(result));
|
|
219
|
+
});
|
|
220
|
+
//
|
|
221
|
+
// Mountpath : '/v1/list/{role, resource, policy, service}{/...}'
|
|
222
|
+
// : '/v1/list/<service>/{role, resource, policy}{/...}'
|
|
223
|
+
//
|
|
224
|
+
// HEAD '/v1/list/.../{path}' : get list on version 1
|
|
225
|
+
// HEADER : X-Auth-Token = User token
|
|
226
|
+
// URL arguments : expand = "true"(default) or "false"
|
|
227
|
+
//
|
|
228
|
+
router.head('/', (req, res, next) => {
|
|
229
|
+
dbglogging_1.default.dlog('CALL:', req.method, req.url);
|
|
230
|
+
if ('HEAD' !== req.method) {
|
|
231
|
+
next();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
res.type('application/json; charset=utf-8');
|
|
235
|
+
//
|
|
236
|
+
// get children list
|
|
237
|
+
//
|
|
238
|
+
const result = rawGetChildrenList(req, false); // Force set expand as false
|
|
239
|
+
const _message = k2hr3dkc_1.default.isResTypeChildrenTree(result) ? k2hr3apiutil_1.default.getSafeString(result.message) : 'Failed to get list.';
|
|
240
|
+
const _status = (k2hr3dkc_1.default.isResTypeChildrenTree(result) && k2hr3apiutil_1.default.isSafeNumber(result.status)) ? result.status : 500;
|
|
241
|
+
if (!k2hr3dkc_1.default.isResTypeChildrenTree(result) || !result.result) {
|
|
242
|
+
dbglogging_1.default.elog(_message);
|
|
243
|
+
k2hr3resutil_1.default.errResponse(req, res, _status);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
dbglogging_1.default.dlog('succeed : ' + _message);
|
|
247
|
+
res.status(204); // 204: No Content
|
|
248
|
+
res.send(JSON.stringify(result));
|
|
249
|
+
});
|
|
250
|
+
//---------------------------------------------------------
|
|
251
|
+
// Exports
|
|
252
|
+
//---------------------------------------------------------
|
|
253
|
+
//
|
|
254
|
+
// Functions
|
|
255
|
+
//
|
|
256
|
+
exports.default = router;
|
|
257
|
+
/*
|
|
258
|
+
* Local variables:
|
|
259
|
+
* tab-width: 4
|
|
260
|
+
* c-basic-offset: 4
|
|
261
|
+
* End:
|
|
262
|
+
* vim600: noexpandtab sw=4 ts=4 fdm=marker
|
|
263
|
+
* vim<600: noexpandtab sw=4 ts=4
|
|
264
|
+
*/
|