-
Notifications
You must be signed in to change notification settings - Fork 0
/
proxy.js
206 lines (178 loc) · 7.13 KB
/
proxy.js
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
200
201
202
203
204
205
206
var net = require('net');
var dns = require('dns');
var date = new Date();
var args = process.argv.slice(2);
if (args.length != 1) {
console.log("Incorrect number of arguments.");
process.exit(1);
}
var clientFacingPort = args[0];
var server = net.createServer(function (clientSocket) {
var haveSeenEndOfHeader = false;
var header = "";
// Should create server socket here to avoid delay on data
var serverSocket = net.Socket();
clientSocket.on('end', function() {
serverSocket.end();
});
serverSocket.on('end', function() {
clientSocket.end();
});
serverSocket.on('error', function(err) {
clientSocket.end();
serverSocket.end();
});
clientSocket.on('error', function(err) {
clientSocket.end();
serverSocket.end();
});
serverSocket.on('data', function(data) {
clientSocket.write(data);
});
// do we need to pass as an argument
clientSocket.on('data', function (data, serverSock) {
if (!haveSeenEndOfHeader) {
var dataString = data.toString('ascii');
header += dataString;
if (header.includes('\r\n\r\n') || header.includes('\n\n')) {
haveSeenEndOfHeader = true;
var trimmedHeader = header.split(/(\r\n\r\n|\n\n)/);
var headerLines = trimmedHeader[0].split(/[\r]?\n/);
var extraData = trimmedHeader[1];
// Take the first line and split it on white space
var requestLineComponents = headerLines.shift().trim().split(/\s+/);
if (requestLineComponents.length != 3) {
console.log("Malformed request line, invalid length");
clientSocket.end();
return;
}
var requestType = requestLineComponents[0].toUpperCase();
var requestURI = requestLineComponents[1];
var requestVersion = requestLineComponents[2].toUpperCase();
if (HTTP_METHODS.indexOf(requestType) == -1){
// Malformed request.
console.log("Malformed request line, method not valid");
clientSocket.end();
return;
}
if (requestVersion != "HTTP/1.1"){
// We only support 1.1
console.log("Unsupported version", requestVersion);
clientSocket.end();
return
}
logRequest(requestType, requestURI)
var optionMap = buildOptionMap(headerLines);
// Modify header fields
requestLineComponents[2] = "HTTP/1.1"
if ("connection" in optionMap) {
optionMap["connection"] = "close";
}
if ("proxy-connection" in optionMap) {
optionMap["proxy-connection"] = "close";
}
if (!("host" in optionMap)) {
// All 1.1 messages should have a host field
clientSocket.end();
return;
}
// Could ipv6 cause there to be multiple : in host?
var hostFieldComponents = optionMap.host.split(':');
var hostName = hostFieldComponents[0];
var hostPort = determineServerPort(hostFieldComponents, requestURI);
function connectToServer(hostname, port) {
// Assign on msg based upon connection type Connect vs Get
// each callback should have a static definition (?)
if (requestType == "CONNECT") {
serverSocket.on("error", function() {
// send 502 bad gateway
var msg = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
clientSocket.write(msg, function() {
clientSocket.end();
});
});
serverSocket.on("connect", function() {
serverSocket.on('error', function() {
clientSocket.end();
});
var msg = "HTTP/1.1 200 OK\r\n\r\n";
clientSocket.write(msg);
});
} else {
// forward modified header + data
serverSocket.on("connect", function() {
var modifiedHeader = buildHTTPHeader(requestLineComponents, optionMap);
serverSocket.write(modifiedHeader + extraData);
});
}
// Connect to Host/Port
serverSocket.connect(hostPort, hostName);
}
dns.lookup(hostName, (err, address, family) => {
if (err) {
console.log('lookup failure');
// some sort of 404 or could not resolve
clientSocket.end();
return;
}
connectToServer(address, hostPort);
});
}
} else {
// we have seen the header
serverSocket.write(data);
}
});
});
server.on('error', (err) => {
console.log("Server error");
process.exit(1);
//TODO: try broadcasting to clients that we hit an error?
})
server.listen(clientFacingPort);
function buildOptionMap(lines) {
var options = {};
for (lineNum in lines) {
var optionComponents = splitHeaderOptionString(lines[lineNum], ":");
if (optionComponents == null) { continue; }
var key = optionComponents[0].trim().toLowerCase();
var value = optionComponents[1].trim();
options[key] = value;
}
return options;
}
function splitHeaderOptionString(s, delim) {
var index = s.indexOf(delim);
if (index < 0) { return null;}
return [s.substring(0, index), s.substring(index + 1, s.length)];
}
function buildHTTPHeader(requestLineComponents, optionMap) {
var header = "";
header += requestLineComponents.join(" ");
header += "\r\n";
for (var optionKey in optionMap) {
header += optionKey + ": " + optionMap[optionKey] + "\r\n";
}
header += "\r\n";
return header;
}
// Checks in the host field and uri for a port. If no port is found, returns 80.
function determineServerPort(hostFieldComponents, requestURI) {
var serverPort = 80;
if (hostFieldComponents.length == 1) {
// Port not included in host field
var portMatches = requestURI.match(/:[0-9]{1,5}/);
if (portMatches != null) {
serverPort = portMatches[0];
}
}else{
// Pull port from host field
serverPort = hostFieldComponents[1];
}
return serverPort;
}
function logRequest(method, uri) {
var time = new Date();
console.log(time + " >>> " + method.toUpperCase() + " " + uri);
};
const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"];