@@ -20,7 +20,9 @@ const Capnp = Npm.require('capnp');
20
20
const Url = Npm . require ( 'url' ) ;
21
21
const Http = Npm . require ( 'http' ) ;
22
22
const Https = Npm . require ( 'https' ) ;
23
+ const Dns = Npm . require ( 'dns' ) ;
23
24
const ApiSession = Capnp . importSystem ( 'sandstorm/api-session.capnp' ) . ApiSession ;
25
+ const WebSession = Capnp . importSystem ( 'sandstorm/web-session.capnp' ) . WebSession ;
24
26
25
27
WrappedUiView = class WrappedUiView {
26
28
constructor ( token , proxy ) {
@@ -83,7 +85,7 @@ ExternalUiView = class ExternalUiView {
83
85
} ;
84
86
}
85
87
86
- return { session : new Capnp . Capability ( new ExternalWebSession ( this . url , this . grainId , options ) , ApiSession ) } ;
88
+ return { session : new Capnp . Capability ( new ExternalWebSession ( this . url , options ) , ApiSession ) } ;
87
89
}
88
90
} ;
89
91
@@ -125,13 +127,17 @@ const responseCodes = {
125
127
505 : { type : 'serverError' } ,
126
128
} ;
127
129
130
+ makeExternalWebSession = function ( url , options ) {
131
+ return new Capnp . Capability ( new ExternalWebSession ( url , options ) , WebSession ) ;
132
+ }
133
+
128
134
ExternalWebSession = class ExternalWebSession {
129
- constructor ( url , grainId , options ) {
135
+ constructor ( url , options ) {
130
136
const parsedUrl = Url . parse ( url ) ;
131
137
this . host = parsedUrl . hostname ;
132
138
this . port = parsedUrl . port ;
133
139
this . protocol = parsedUrl . protocol ;
134
- this . grainId = grainId ;
140
+ this . path = parsedUrl . path ;
135
141
this . options = options || { } ;
136
142
}
137
143
@@ -165,129 +171,151 @@ ExternalWebSession = class ExternalWebSession {
165
171
const _this = this ;
166
172
const session = _this ;
167
173
return new Promise ( ( resolve , reject ) => {
168
- const options = _ . clone ( session . options ) ;
169
- options . headers = options . headers || { } ;
170
- options . path = path ;
171
- options . method = method ;
172
- if ( contentType ) {
173
- options . headers [ 'content-type' ] = contentType ;
174
- }
175
-
176
- // set accept header
177
- if ( 'accept' in context ) {
178
- options . headers . accept = context . accept . map ( ( acceptedType ) => {
179
- return acceptedType . mimeType + '; ' + acceptedType . qValue ;
180
- } ) . join ( ', ' ) ;
181
- } else if ( ! ( 'accept' in options . headers ) ) {
182
- options . headers . accept = '*/*' ;
183
- }
184
-
185
- // set cookies
186
- if ( context . cookies && context . cookies . length > 0 ) {
187
- options . headers . cookies = options . headers . cookies || '' ;
188
- context . cookies . forEach ( ( keyVal ) => {
189
- options . headers . cookies += keyVal . key + '=' + keyVal . val + ',' ;
190
- } ) ;
191
- options . headers . cookies = options . headers . cookies . slice ( 0 , - 1 ) ;
192
- }
193
-
194
- options . host = session . host ;
195
- options . port = session . port ;
196
-
197
- let requestMethod = Http . request ;
198
- if ( session . protocol === 'https:' ) {
199
- requestMethod = Https . request ;
200
- }
201
-
202
- req = requestMethod ( options , ( resp ) => {
203
- const buffers = [ ] ;
204
- const statusInfo = responseCodes [ resp . statusCode ] ;
205
-
206
- const rpcResponse = { } ;
207
-
208
- switch ( statusInfo . type ) {
209
- case 'content' :
210
- resp . on ( 'data' , ( buf ) => {
211
- buffers . push ( buf ) ;
212
- } ) ;
213
-
214
- resp . on ( 'end' , ( ) => {
215
- const content = { } ;
216
- rpcResponse . content = content ;
217
-
218
- content . statusCode = statusInfo . code ;
219
- if ( 'content-encoding' in resp . headers ) content . encoding = resp . headers [ 'content-encoding' ] ;
220
- if ( 'content-language' in resp . headers ) content . language = resp . headers [ 'content-language' ] ;
221
- if ( 'content-type' in resp . headers ) content . language = resp . headers [ 'content-type' ] ;
222
- if ( 'content-disposition' in resp . headers ) {
223
- const disposition = resp . headers [ 'content-disposition' ] ;
224
- const parts = disposition . split ( ';' ) ;
225
- if ( parts [ 0 ] . toLowerCase ( ) . trim ( ) === 'attachment' ) {
226
- parts . forEach ( ( part ) => {
227
- const splitPart = part . split ( '=' ) ;
228
- if ( splitPart [ 0 ] . toLowerCase ( ) . trim ( ) === 'filename' ) {
229
- content . disposition = { download : splitPart [ 1 ] . trim ( ) } ;
230
- }
231
- } ) ;
174
+ Dns . lookup ( _this . host , 4 , ( err , address ) => { // TODO(someday): handle ipv6
175
+ if ( err ) {
176
+ reject ( err ) ;
177
+ return ;
178
+ }
179
+ if ( address . lastIndexOf ( "10." , 0 ) === 0 ||
180
+ address . lastIndexOf ( "127." , 0 ) === 0 ||
181
+ address . lastIndexOf ( "192.168." , 0 ) === 0 ) {
182
+ // Block the most commonly used private ip ranges as a security measure.
183
+ reject ( "Domain resolved to an invalid IP: " + address ) ;
184
+ return ;
185
+ }
186
+ const options = _ . clone ( session . options ) ;
187
+ options . headers = options . headers || { } ;
188
+ if ( _this . path ) {
189
+ options . path = _this . path ;
190
+ if ( _this . path [ _this . path . length - 1 ] !== "/" ) {
191
+ options . path += "/" ;
192
+ }
193
+ options . path += path ;
194
+ } else {
195
+ options . path = path ;
196
+ }
197
+ options . path = ( _this . path || "" ) + path ;
198
+ options . method = method ;
199
+ if ( contentType ) {
200
+ options . headers [ 'content-type' ] = contentType ;
201
+ }
202
+
203
+ // set accept header
204
+ if ( 'accept' in context ) {
205
+ options . headers . accept = context . accept . map ( ( acceptedType ) => {
206
+ return acceptedType . mimeType + '; ' + acceptedType . qValue ;
207
+ } ) . join ( ', ' ) ;
208
+ } else if ( ! ( 'accept' in options . headers ) ) {
209
+ options . headers . accept = '*/*' ;
210
+ }
211
+
212
+ // set cookies
213
+ if ( context . cookies && context . cookies . length > 0 ) {
214
+ options . headers . cookies = options . headers . cookies || '' ;
215
+ context . cookies . forEach ( ( keyVal ) => {
216
+ options . headers . cookies += keyVal . key + '=' + keyVal . val + ',' ;
217
+ } ) ;
218
+ options . headers . cookies = options . headers . cookies . slice ( 0 , - 1 ) ;
219
+ }
220
+
221
+ options . host = session . host ;
222
+ options . port = session . port ;
223
+
224
+ let requestMethod = Http . request ;
225
+ if ( session . protocol === 'https:' ) {
226
+ requestMethod = Https . request ;
227
+ }
228
+
229
+ req = requestMethod ( options , ( resp ) => {
230
+ const buffers = [ ] ;
231
+ const statusInfo = responseCodes [ resp . statusCode ] ;
232
+
233
+ const rpcResponse = { } ;
234
+
235
+ switch ( statusInfo . type ) {
236
+ case 'content' :
237
+ resp . on ( 'data' , ( buf ) => {
238
+ buffers . push ( buf ) ;
239
+ } ) ;
240
+
241
+ resp . on ( 'end' , ( ) => {
242
+ const content = { } ;
243
+ rpcResponse . content = content ;
244
+
245
+ content . statusCode = statusInfo . code ;
246
+ if ( 'content-encoding' in resp . headers ) content . encoding = resp . headers [ 'content-encoding' ] ;
247
+ if ( 'content-language' in resp . headers ) content . language = resp . headers [ 'content-language' ] ;
248
+ if ( 'content-type' in resp . headers ) content . language = resp . headers [ 'content-type' ] ;
249
+ if ( 'content-disposition' in resp . headers ) {
250
+ const disposition = resp . headers [ 'content-disposition' ] ;
251
+ const parts = disposition . split ( ';' ) ;
252
+ if ( parts [ 0 ] . toLowerCase ( ) . trim ( ) === 'attachment' ) {
253
+ parts . forEach ( ( part ) => {
254
+ const splitPart = part . split ( '=' ) ;
255
+ if ( splitPart [ 0 ] . toLowerCase ( ) . trim ( ) === 'filename' ) {
256
+ content . disposition = { download : splitPart [ 1 ] . trim ( ) } ;
257
+ }
258
+ } ) ;
259
+ }
232
260
}
233
- }
234
261
235
- content . body = { } ;
236
- content . body . bytes = Buffer . concat ( buffers ) ;
262
+ content . body = { } ;
263
+ content . body . bytes = Buffer . concat ( buffers ) ;
237
264
265
+ resolve ( rpcResponse ) ;
266
+ } ) ;
267
+ break ;
268
+ case 'noContent' :
269
+ const noContent = { } ;
270
+ rpcResponse . noContent = noContent ;
271
+ noContent . setShouldResetForm = statusInfo . shouldResetForm ;
238
272
resolve ( rpcResponse ) ;
239
- } ) ;
240
- break ;
241
- case 'noContent' :
242
- const noContent = { } ;
243
- rpcResponse . noContent = noContent ;
244
- noContent . setShouldResetForm = statusInfo . shouldResetForm ;
245
- resolve ( rpcResponse ) ;
246
- break ;
247
- case 'redirect' :
248
- const redirect = { } ;
249
- rpcResponse . redirect = redirect ;
250
- redirect . isPermanent = statusInfo . isPermanent ;
251
- redirect . switchToGet = statusInfo . switchToGet ;
252
- if ( 'location' in resp . headers ) redirect . location = resp . headers . location ;
253
- resolve ( rpcResponse ) ;
254
- break ;
255
- case 'clientError' :
256
- const clientError = { } ;
257
- rpcResponse . clientError = clientError ;
258
- clientError . statusCode = statusInfo . clientErrorCode ;
259
- clientError . descriptionHtml = statusInfo . descriptionHtml ;
260
- resolve ( rpcResponse ) ;
261
- break ;
262
- case 'serverError' :
263
- const serverError = { } ;
264
- rpcResponse . serverError = serverError ;
265
- clientError . descriptionHtml = statusInfo . descriptionHtml ;
266
- resolve ( rpcResponse ) ;
267
- break ;
268
- default : // ???
269
- err = new Error ( 'Invalid status code ' + resp . statusCode + ' received in response.' ) ;
270
- reject ( err ) ;
271
- break ;
272
- }
273
- } ) ;
273
+ break ;
274
+ case 'redirect' :
275
+ const redirect = { } ;
276
+ rpcResponse . redirect = redirect ;
277
+ redirect . isPermanent = statusInfo . isPermanent ;
278
+ redirect . switchToGet = statusInfo . switchToGet ;
279
+ if ( 'location' in resp . headers ) redirect . location = resp . headers . location ;
280
+ resolve ( rpcResponse ) ;
281
+ break ;
282
+ case 'clientError' :
283
+ const clientError = { } ;
284
+ rpcResponse . clientError = clientError ;
285
+ clientError . statusCode = statusInfo . clientErrorCode ;
286
+ clientError . descriptionHtml = statusInfo . descriptionHtml ;
287
+ resolve ( rpcResponse ) ;
288
+ break ;
289
+ case 'serverError' :
290
+ const serverError = { } ;
291
+ rpcResponse . serverError = serverError ;
292
+ clientError . descriptionHtml = statusInfo . descriptionHtml ;
293
+ resolve ( rpcResponse ) ;
294
+ break ;
295
+ default : // ???
296
+ err = new Error ( 'Invalid status code ' + resp . statusCode + ' received in response.' ) ;
297
+ reject ( err ) ;
298
+ break ;
299
+ }
300
+ } ) ;
274
301
275
- req . on ( 'error' , ( e ) => {
276
- reject ( e ) ;
277
- } ) ;
302
+ req . on ( 'error' , ( e ) => {
303
+ reject ( e ) ;
304
+ } ) ;
278
305
279
- req . setTimeout ( 15000 , ( ) => {
280
- req . abort ( ) ;
281
- err = new Error ( 'Request timed out.' ) ;
282
- err . kjType = 'overloaded' ;
283
- reject ( err ) ;
284
- } ) ;
306
+ req . setTimeout ( 15000 , ( ) => {
307
+ req . abort ( ) ;
308
+ err = new Error ( 'Request timed out.' ) ;
309
+ err . kjType = 'overloaded' ;
310
+ reject ( err ) ;
311
+ } ) ;
285
312
286
- if ( content ) {
287
- req . end ( content ) ;
288
- } else {
289
- req . end ( ) ;
290
- }
313
+ if ( content ) {
314
+ req . end ( content ) ;
315
+ } else {
316
+ req . end ( ) ;
317
+ }
318
+ } ) ;
291
319
} ) ;
292
320
}
293
321
} ;
0 commit comments