Skip to content

Commit fabe7aa

Browse files
committed
#28 remove dependencies
1 parent a8baf91 commit fabe7aa

File tree

3 files changed

+223
-55
lines changed

3 files changed

+223
-55
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
easy to use http server that allows for using javascript just as php was used back in the days
44

5-
> [!WARNING]
6-
> as the issues indicate, a lot of breaking changes are ahead.
7-
> make sure to check for these in future until version 1.0.0 is relessed.
5+
> [!NOTE]
6+
> this project has not reached v1.0.0 yet, so breaking changes _may appear_ in the future
87
98
## usage
109

@@ -25,14 +24,14 @@ and now http://localhost:8080 offers a greeting!
2524
for developing rtjscomp itself, clone this repository and run
2625

2726
```console
28-
$ npm install
2927
$ npm start
3028
```
3129

3230
## rtjscomp.json (optional)
3331

3432
it is only needed if you want to change default settings. it has the following properties:
3533

34+
- `compress`: boolean, set to false to disable any compression
3635
- `gzip_level`: compression level, 0-9, default is 9
3736
- `log_verbose`: boolean, set true for more verbose logging or run with -v
3837
- `path_aliases`: object where the key is the url path and the value is the file path
@@ -45,6 +44,7 @@ it is only needed if you want to change default settings. it has the following p
4544
- `type_dynamics`: dynamic file types, see [api](#api)
4645
- `type_mimes`: file type to mime type map (most common are already set, only overrides)
4746
- `type_raws`: array of file types that are sent uncompressed
47+
- `zstd_level`: compression level for zstd, 0-19, default is 3
4848

4949
here is an example for a customized setup:
5050

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rtjscomp",
3-
"version": "0.9.6",
3+
"version": "0.9.7",
44
"description": "php-like server but with javascript",
55
"repository": {
66
"type": "git",
@@ -9,11 +9,6 @@
99
"bugs": {
1010
"url": "https://github.com/L3P3/rtjscomp/issues"
1111
},
12-
"dependencies": {
13-
"ipware": "latest",
14-
"parse-multipart-data": "latest",
15-
"querystring": "latest"
16-
},
1712
"scripts": {
1813
"start": "./rtjscomp.js",
1914
"build": "./build.js"

rtjscomp.js

Lines changed: 218 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ const http = require('http');
1414
const url = require('url');
1515
const zlib = require('zlib');
1616

17-
// external libs
18-
const multipart_parse = require('parse-multipart-data').parse;
19-
const querystring_parse = require('querystring').decode;
20-
const request_ip_get = require('ipware')().get_ip;
21-
2217
// constants
2318
const COMPRESS_METHOD_NONE = 0;
2419
const COMPRESS_METHOD_GZIP = 1;
@@ -720,7 +715,7 @@ const parse_old_map = data => (
720715
entry.length > 0 &&
721716
entry.charCodeAt(0) !== 35
722717
)
723-
.map(entry => entry.split(':'))
718+
.map(entry => entry.split(':', 2))
724719
)
725720
)
726721
const config_path_check = (path, allow_empty = false) => {
@@ -784,10 +779,190 @@ const spam = (type, data) => {
784779
}
785780
}
786781

782+
const multipart_parse = (body_raw, boundary, result) => {
783+
boundary = Buffer.from(boundary);
784+
if (boundary[0] === 34) {
785+
boundary = boundary.subarray(1, -1);
786+
}
787+
const boundary_length = boundary.length;
788+
/// -- + boundary + \r\n
789+
const boundary_length_full = boundary_length + 4;
790+
/// last possible index for full boundary
791+
const boundary_index_limit = body_raw.length - boundary_length_full;
792+
let index_start = 0;
793+
let index_end = 0;
794+
while (
795+
// find next boundary
796+
(
797+
index_end = body_raw.indexOf(45, index_end) // -
798+
) >= 0 &&
799+
index_end <= boundary_index_limit
800+
) {
801+
// check if it actually is a boundary
802+
if (
803+
body_raw[index_end + 1] !== 45 || // -
804+
index_end > 0 && body_raw[index_end - 2] !== 13 || // \r
805+
index_end > 0 && body_raw[index_end - 1] !== 10 || // \n
806+
body_raw.compare(
807+
boundary,
808+
0,
809+
boundary_length,
810+
index_end + 2,
811+
index_end + 2 + boundary_length
812+
) !== 0
813+
) {
814+
++index_end;
815+
continue;
816+
}
817+
// skip first boundary
818+
if (index_start > 0) {
819+
if (index_end - index_start < 20) {
820+
throw 'part too short';
821+
}
822+
// find end of header
823+
let index_header_end = index_start;
824+
while (
825+
(
826+
index_header_end = body_raw.indexOf(13, index_header_end) // \r
827+
) >= 0 &&
828+
body_raw[index_header_end + 1] !== 10 || // \n
829+
body_raw[index_header_end + 2] !== 13 || // \r
830+
body_raw[index_header_end + 3] !== 10 // \n
831+
) {
832+
++index_header_end;
833+
}
834+
if (index_header_end < 0) {
835+
throw 'part header end not found';
836+
}
837+
// parse header
838+
let header_disposition = '';
839+
let header_content_type = '';
840+
for (
841+
const header_line of
842+
body_raw
843+
.toString(
844+
'utf8',
845+
index_start,
846+
index_header_end
847+
)
848+
.split('\r\n', 2)
849+
) {
850+
if (!header_line.startsWith('Content-')) {
851+
throw 'illegal part header';
852+
}
853+
const [name, value] = header_line.slice(8).split(': ', 2);
854+
if (name === 'Disposition') {
855+
if (!value.startsWith('form-data; ')) {
856+
throw 'illegal disposition part header';
857+
}
858+
header_disposition = value.slice(11);
859+
}
860+
else if (name === 'Type') {
861+
header_content_type = value;
862+
}
863+
// else if (name === 'Transfer-Encoding') {}
864+
else {
865+
throw 'illegal part header';
866+
}
867+
}
868+
if (!header_disposition) {
869+
throw 'part disposition header missing';
870+
}
871+
let header_disposition_name = '';
872+
let header_disposition_filename = '';
873+
for (const header_disposition_entry of header_disposition.split('; ')) {
874+
let [name, value] = header_disposition_entry.split('=', 2);
875+
if (value.charCodeAt(0) === 34) {
876+
value = value.slice(1, -1);
877+
}
878+
if (name === 'name') {
879+
header_disposition_name = value;
880+
}
881+
else if (name === 'filename') {
882+
header_disposition_filename = value;
883+
}
884+
else {
885+
throw 'illegal disposition part header';
886+
}
887+
}
888+
if (!header_disposition_name) {
889+
throw 'part name header missing';
890+
}
891+
const part_result = (
892+
header_content_type
893+
? {
894+
filename: header_disposition_filename,
895+
data: body_raw.subarray(
896+
index_header_end + 4,
897+
index_end - 2
898+
),
899+
type: header_content_type,
900+
}
901+
: (
902+
body_raw
903+
.toString(
904+
'utf8',
905+
index_header_end + 4,
906+
index_end - 2
907+
)
908+
)
909+
);
910+
if (header_disposition_name.endsWith('[]')) {
911+
header_disposition_name = header_disposition_name.slice(0, -2);
912+
if (header_disposition_name in result) {
913+
if (!(result[header_disposition_name] instanceof Array)) {
914+
throw 'duplicate part name';
915+
}
916+
result[header_disposition_name].push(part_result);
917+
}
918+
else {
919+
result[header_disposition_name] = [part_result];
920+
}
921+
}
922+
else {
923+
if (header_disposition_name in result) {
924+
throw 'duplicate part name';
925+
}
926+
result[header_disposition_name] = part_result;
927+
}
928+
}
929+
index_start = index_end + boundary_length_full;
930+
index_end = index_start + 1;
931+
}
932+
}
933+
934+
const querystring_parse = (querystring, result) => {
935+
for (const entry of querystring.split('&')) {
936+
let [key, value] = entry.split('=', 2);
937+
const array = key.endsWith('[]');
938+
if (array) {
939+
key = key.slice(0, -2);
940+
}
941+
if (!key) {
942+
throw 'key is empty';
943+
}
944+
value = decodeURIComponent(value || '');
945+
if (key in result) {
946+
if ((result[key] instanceof Array) !== array) {
947+
throw 'type mismatch';
948+
}
949+
if (array) {
950+
result[key].push(value);
951+
}
952+
else {
953+
result[key] = value;
954+
}
955+
}
956+
else {
957+
result[key] = array ? [value] : value;
958+
}
959+
}
960+
}
961+
787962
const request_handle = async (request, response, https) => {
788963
const request_method = request.method;
789964
const request_headers = request.headers;
790-
const request_ip = request_ip_get(request).clientIp;
965+
const request_ip = request_headers['x-forwarded-for'] || request.socket.remoteAddress;
791966
const request_method_head = request_method === 'HEAD';
792967

793968
if ('x-forwarded-proto' in request_headers) {
@@ -1077,21 +1252,21 @@ const request_handle = async (request, response, https) => {
10771252
}
10781253

10791254
if (request_headers['x-input']) {
1080-
Object.assign(
1081-
file_function_input,
1082-
querystring_parse(request_headers['x-input'])
1083-
);
1255+
try {
1256+
querystring_parse(request_headers['x-input'], file_function_input);
1257+
}
1258+
catch (err) {
1259+
log(`[error] ${path} request x-input: ${err}`);
1260+
throw 400;
1261+
}
10841262
}
10851263

10861264
if (request_url_parsed.query) {
10871265
try {
1088-
Object.assign(
1089-
file_function_input,
1090-
querystring_parse(request_url_parsed.query)
1091-
);
1266+
querystring_parse(request_url_parsed.query, file_function_input);
10921267
}
10931268
catch (err) {
1094-
log(`[error] ${path} request query: ${err.message}`);
1269+
log(`[error] ${path} request query: ${err}`);
10951270
throw 400;
10961271
}
10971272
}
@@ -1100,37 +1275,32 @@ const request_handle = async (request, response, https) => {
11001275
try {
11011276
const content_type = request.headers['content-type'] || '';
11021277
const body_raw = file_function_input['body'] = await request_body_promise;
1103-
let body = null;
1104-
switch (content_type.split(';')[0]) {
1105-
case 'application/x-www-form-urlencoded':
1106-
body = querystring_parse(body_raw.toString());
1107-
break;
1108-
case 'application/json':
1109-
body = JSON.parse(body_raw.toString());
1110-
break;
1111-
case 'multipart/form-data': {
1112-
body = Object.fromEntries(
1113-
multipart_parse(
1114-
body_raw,
1115-
content_type.split('boundary=')[1].split(';')[0]
1116-
)
1117-
.map(value => {
1118-
const {name} = value;
1119-
delete value.name;
1120-
return [
1121-
name,
1122-
value.type ? value : value.data.toString()
1123-
];
1124-
})
1125-
);
1126-
}
1127-
}
1128-
if (body) {
1129-
Object.assign(file_function_input, /** @type {!Object} */ (body));
1278+
switch (
1279+
content_type.split(';', 1)[0]
1280+
) {
1281+
case 'application/json':
1282+
Object.assign(
1283+
file_function_input,
1284+
/** @type {!Object} */ (
1285+
JSON.parse(body_raw.toString())
1286+
)
1287+
);
1288+
break;
1289+
case 'application/x-www-form-urlencoded':
1290+
querystring_parse(body_raw.toString(), file_function_input);
1291+
break;
1292+
case 'multipart/form-data':
1293+
multipart_parse(
1294+
body_raw,
1295+
content_type
1296+
.split('boundary=', 2)[1]
1297+
.split(';', 1)[0],
1298+
file_function_input
1299+
);
11301300
}
11311301
}
11321302
catch (err) {
1133-
log(`[error] ${path} request body: ${err.message}`);
1303+
log(`[error] ${path} request body: ${err.message || err}`);
11341304
throw 400;
11351305
}
11361306
}
@@ -1178,7 +1348,10 @@ const request_handle = async (request, response, https) => {
11781348
).pipe(response);
11791349
}
11801350
}
1181-
else if (encodings.includes('gzip')) {
1351+
else if (
1352+
GZIP_OPTIONS.level > 0 &&
1353+
encodings.includes('gzip')
1354+
) {
11821355
file_compression = COMPRESS_METHOD_GZIP;
11831356
response.setHeader('Content-Encoding', 'gzip');
11841357
if (!request_method_head) {

0 commit comments

Comments
 (0)