@@ -20,7 +20,9 @@ const Capnp = Npm.require('capnp');
2020const Url = Npm . require ( 'url' ) ;
2121const Http = Npm . require ( 'http' ) ;
2222const Https = Npm . require ( 'https' ) ;
23+ const Dns = Npm . require ( 'dns' ) ;
2324const ApiSession = Capnp . importSystem ( 'sandstorm/api-session.capnp' ) . ApiSession ;
25+ const WebSession = Capnp . importSystem ( 'sandstorm/web-session.capnp' ) . WebSession ;
2426
2527WrappedUiView = class WrappedUiView {
2628 constructor ( token , proxy ) {
@@ -83,7 +85,7 @@ ExternalUiView = class ExternalUiView {
8385 } ;
8486 }
8587
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 ) } ;
8789 }
8890} ;
8991
@@ -125,13 +127,17 @@ const responseCodes = {
125127 505 : { type : 'serverError' } ,
126128} ;
127129
130+ makeExternalWebSession = function ( url , options ) {
131+ return new Capnp . Capability ( new ExternalWebSession ( url , options ) , WebSession ) ;
132+ }
133+
128134ExternalWebSession = class ExternalWebSession {
129- constructor ( url , grainId , options ) {
135+ constructor ( url , options ) {
130136 const parsedUrl = Url . parse ( url ) ;
131137 this . host = parsedUrl . hostname ;
132138 this . port = parsedUrl . port ;
133139 this . protocol = parsedUrl . protocol ;
134- this . grainId = grainId ;
140+ this . path = parsedUrl . path ;
135141 this . options = options || { } ;
136142 }
137143
@@ -165,129 +171,151 @@ ExternalWebSession = class ExternalWebSession {
165171 const _this = this ;
166172 const session = _this ;
167173 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+ }
232260 }
233- }
234261
235- content . body = { } ;
236- content . body . bytes = Buffer . concat ( buffers ) ;
262+ content . body = { } ;
263+ content . body . bytes = Buffer . concat ( buffers ) ;
237264
265+ resolve ( rpcResponse ) ;
266+ } ) ;
267+ break ;
268+ case 'noContent' :
269+ const noContent = { } ;
270+ rpcResponse . noContent = noContent ;
271+ noContent . setShouldResetForm = statusInfo . shouldResetForm ;
238272 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+ } ) ;
274301
275- req . on ( 'error' , ( e ) => {
276- reject ( e ) ;
277- } ) ;
302+ req . on ( 'error' , ( e ) => {
303+ reject ( e ) ;
304+ } ) ;
278305
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+ } ) ;
285312
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+ } ) ;
291319 } ) ;
292320 }
293321} ;
0 commit comments