1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Defines a servlet that handles all communication between the client and
17 the server."""
18
19 import re
20 import logging
21 import mimetypes
22
23 from time import time
24 from warnings import warn
25
26 from urlparse import urljoin
27 from os.path import exists, getmtime
28
29 try:
30 from StringIO import StringIO
31 except ImportError, e:
32 from StringIO import StringIO
33
34 from muntjac.util import clsname
35 from muntjac.application import Application
36
37 from muntjac.terminal.gwt.server.constants import Constants
38 from muntjac.terminal.gwt.server.json_paint_target import JsonPaintTarget
39 from muntjac.terminal.gwt.server.exceptions import ServletException
40
41 from muntjac.terminal.uri_handler import IErrorEvent as URIHandlerErrorEvent
42 from muntjac.terminal.terminal import IErrorEvent as TerminalErrorEvent
43
44 from muntjac.terminal.gwt.server.paste_wsgi_servlet import PasteWsgiServlet
45
46 from muntjac.terminal.gwt.server.exceptions import \
47 SessionExpiredException, SystemMessageException
48
49 from muntjac.terminal.gwt.client.application_connection import \
50 ApplicationConnection
51
52 from muntjac.terminal.gwt.server.web_application_context import \
53 WebApplicationContext
54
55 from muntjac.terminal.gwt.server.http_servlet_request_listener import \
56 IHttpServletRequestListener
57
58 from muntjac.terminal.parameter_handler import \
59 IErrorEvent as ParameterHandlerErrorEvent
60
61
62 logger = logging.getLogger(__name__)
78
81 """Abstract implementation of the ApplicationServlet which handles all
82 communication between the client and the server.
83
84 It is possible to extend this class to provide own functionality but in
85 most cases this is unnecessary.
86
87 @author: Vaadin Ltd.
88 @author: Richard Lincoln
89 @version: 1.1.2
90 """
91
92
93
94
95 VERSION = None
96
97
98 VERSION_MAJOR = None
99
100
101 VERSION_MINOR = None
102
103
104 VERSION_REVISION = None
105
106
107
108 VERSION_BUILD = None
109
110
111 if '1.1.2' == '@' + 'VERSION' + '@':
112 VERSION = '9.9.9.INTERNAL-DEBUG-BUILD'
113 else:
114 VERSION = '1.1.2'
115
116 digits = VERSION.split('.', 4)
117 VERSION_MAJOR = int(digits[0])
118 VERSION_MINOR = int(digits[1])
119 VERSION_REVISION = int(digits[2])
120
121 if len(digits) == 4:
122 VERSION_BUILD = digits[3]
123 else:
124 VERSION_BUILD = ''
125
126
127
128
129
130
131 REQUEST_FRAGMENT = ''
132
133
134
135
136
137
138
139 REQUEST_VAADIN_STATIC_FILE_PATH = ''
140
141
142
143
144
145
146
147 REQUEST_WIDGETSET = ''
148
149
150
151
152
153
154
155 REQUEST_SHARED_WIDGETSET = ''
156
157
158
159
160
161
162
163 REQUEST_DEFAULT_THEME = ''
164
165
166
167
168
169
170
171
172 REQUEST_APPSTYLE = ''
173
174 UPLOAD_URL_PREFIX = 'APP/UPLOAD/'
175
176
177 - def __init__(self, productionMode=False, debug=False, widgetset=None,
178 resourceCacheTime=3600, disableXsrfProtection=False,
179 *args, **kw_args):
206
207
209 """Called by the servlet container to indicate to a servlet that the
210 servlet is being placed into service.
211 """
212
213
214
215
216
217
218
219
220
221
222 if self._firstTransaction:
223 self._firstTransaction = False
224 self.checkProductionMode()
225 self.checkCrossSiteProtection()
226 self.checkResourceCacheTime()
227
228
236
237
246
247
265
266
277
278
280 """Gets an application property value.
281
282 @param parameterName:
283 the Name or the parameter.
284 @return: String value or C{None} if not found
285 """
286 val = self._applicationProperties.get(parameterName)
287 if val is not None:
288 return val
289
290
291 val = self._applicationProperties.get(parameterName.lower())
292 return val
293
294
296 """Gets an system property value.
297
298 @param parameterName:
299 the Name or the parameter.
300 @return: String value or C{None} if not found
301 """
302 raise NotImplementedError
303
304
306 """Gets an application or system property value.
307
308 @param parameterName:
309 the Name or the parameter.
310 @param defaultValue:
311 the Default to be used.
312 @return: String value or default if not found
313 """
314
315 val = self.getApplicationProperty(parameterName)
316 if val is not None:
317 return val
318
319
320
321
322
323
324 return defaultValue
325
326
328 """Returns true if the servlet is running in production mode.
329 Production mode disables all debug facilities.
330
331 @return: true if in production mode, false if in debug mode
332 """
333 return self._productionMode
334
335
337 """Returns the amount of milliseconds the browser should cache a file.
338 Default is 1 hour (3600 ms).
339
340 @return: The amount of milliseconds files are cached in the browser
341 """
342 return self._resourceCacheTime
343
344
345 - def service(self, request, response):
346 """Receives standard HTTP requests from the public service method and
347 dispatches them.
348 """
349 requestType = self.getRequestType(request)
350 if not self.ensureCookiesEnabled(requestType, request, response):
351 return
352
353 if requestType == RequestType.STATIC_FILE:
354 self.serveStaticResources(request, response)
355 return
356
357 if self.isRepaintAll(request):
358
359 self.checkWidgetsetVersion(request)
360
361 application = None
362 transactionStarted = False
363 requestStarted = False
364
365 try:
366
367
368
369
370
371
372
373
374
375 if (requestType == RequestType.UIDL
376 and ApplicationConnection.PARAM_UNLOADBURST
377 in self.getParameters(request)
378 and self.getContentLength(request) < 1
379 and self.getExistingApplication(request, False) is None):
380 self.redirectToApplication(request, response)
381 return
382
383
384 application = self.findApplicationInstance(request, requestType)
385 if application is None:
386 return
387
388
389
390 webApplicationContext = \
391 self.getApplicationContext(self.getSession(request))
392 applicationManager = \
393 webApplicationContext.getApplicationManager(
394 application, self)
395
396
397 self.updateBrowserProperties(
398 webApplicationContext.getBrowser(), request)
399
400
401
402 if isinstance(application, IHttpServletRequestListener):
403 application.onRequestStart(request, response)
404 requestStarted = True
405
406
407 self.startApplication(request, application, webApplicationContext)
408
409
410
411 webApplicationContext.startTransaction(application, request)
412 transactionStarted = True
413
414
415 if requestType == RequestType.FILE_UPLOAD:
416 applicationManager.handleFileUpload(request, response, self)
417 return
418 elif requestType == RequestType.UIDL:
419
420 window = applicationManager.getApplicationWindow(request,
421 self, application, None)
422 applicationManager.handleUidlRequest(request, response,
423 self, window)
424 return
425
426
427
428 if not application.isRunning():
429 self.endApplication(request, response, application)
430 return
431
432
433 window = self.getApplicationWindow(request, applicationManager,
434 application)
435 if window is None:
436 raise ServletException(self.ERROR_NO_WINDOW_FOUND)
437
438
439 if window.getTerminal() is None:
440 window.setTerminal(webApplicationContext.getBrowser())
441
442
443 parameters = request.fields()
444 if window is not None and parameters is not None:
445 window.handleParameters(parameters)
446
447
448
449 if self.handleURI(applicationManager, window, request, response):
450 return
451
452
453 self.writeAjaxPage(request, response, window, application)
454
455 except SessionExpiredException, e:
456
457 self.handleServiceSessionExpired(request, response)
458
459
460
461
462 finally:
463
464 try:
465 if transactionStarted:
466 application.getContext().endTransaction(application,
467 request)
468 finally:
469 if requestStarted:
470 application.onRequestEnd(request, response)
471
472
500
501
503
504 browser.updateRequestDetails(self.getLocale(request),
505 self.getHeader(request, 'REMOTE_ADDR'),
506 self.isSecure(request),
507 self.getUserAgent(request))
508
509 if request.field('repaintAll', None) is not None:
510 browser.updateClientSideDetails(
511 self.getParameter(request, 'sw', None),
512 self.getParameter(request, 'sh', None),
513 self.getParameter(request, 'tzo', None),
514 self.getParameter(request, 'rtzo', None),
515 self.getParameter(request, 'dstd', None),
516 self.getParameter(request, 'dston', None),
517 self.getParameter(request, 'curdate', None),
518 self.getParameter(request, 'td', None) is not None)
519
520
523 """Send a notification to client's application. Used to notify
524 client of critical errors, session expiration and more. Server
525 has no knowledge of what application client refers to.
526
527 @param request:
528 the HTTP request instance.
529 @param response:
530 the HTTP response to write to.
531 @param caption:
532 the notification caption
533 @param message:
534 to notification body
535 @param details:
536 a detail message to show in addition to the message.
537 Currently shown directly below the message but could be
538 hidden behind a details drop down in the future. Mainly
539 used to give additional information not necessarily
540 useful to the end user.
541 @param url:
542 url to load when the message is dismissed. Null will
543 reload the current page.
544 @raise IOException:
545 if the writing failed due to input/output error.
546 """
547 if self.isUIDLRequest(request):
548
549 if caption is not None:
550 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"'
551
552 if details is not None:
553 if message is None:
554 message = details
555 else:
556 message += '<br/><br/>' + details
557
558 if message is not None:
559 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"'
560 else:
561 message = 'null'
562
563 if url is not None:
564 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"'
565 else:
566 url = 'null'
567
568 output = ('for(;;);[{\"changes\":[], \"meta\" : {'
569 '\"appError\": {' + '\"caption\":' + caption + ','
570 '\"message\" : ' + message + ',' + '\"url\" : ' + url +
571 '}}, \"resources\": {}, \"locales\":[]}]')
572
573 self.writeResponse(
574 response, 'application/json; charset=UTF-8', output)
575 else:
576
577 output = ''
578
579 if url is not None:
580 output += '<a href=\"' + url + '\">'
581
582 if caption is not None:
583 output += '<b>' + caption + '</b><br/>'
584
585 if message is not None:
586 output += message
587 output += '<br/><br/>'
588
589 if details is not None:
590 output += details
591 output += '<br/><br/>'
592
593 if url is not None:
594 output += '</a>'
595
596 self.writeResponse(response, 'text/html; charset=UTF-8', output)
597
598
600 """Writes the response in C{output} using the contentType given
601 in C{contentType} to the provided L{HttpServletResponse}
602
603 @param response:
604 @param contentType:
605 @param output:
606 Output to write (UTF-8 encoded)
607 @raise IOException:
608 """
609 self.setHeader(response, 'Content-Type', contentType)
610 self.write(response, output)
611
612
614 """Returns the application instance to be used for the request. If
615 an existing instance is not found a new one is created or null is
616 returned to indicate that the application is not available.
617
618 @raise ServletException:
619 @raise SessionExpiredException:
620 """
621 requestCanCreateApplication = \
622 self.requestCanCreateApplication(request, requestType)
623
624
625 application = self.getExistingApplication(request,
626 requestCanCreateApplication)
627
628 if application is not None:
629
630
631 restartApplication = self.getParameter(request,
632 self.URL_PARAMETER_RESTART_APPLICATION, None) is not None
633
634 closeApplication = self.getParameter(request,
635 self.URL_PARAMETER_CLOSE_APPLICATION, None) is not None
636
637 if restartApplication:
638 session = self.getSession(request, False)
639 self.closeApplication(application, session)
640 return self.createApplication(request)
641
642 elif closeApplication:
643 session = self.getSession(request, False)
644 self.closeApplication(application, session)
645 return None
646
647 else:
648 return application
649
650
651 if requestCanCreateApplication:
652
653
654 return self.createApplication(request)
655
656 else:
657
658
659 raise SessionExpiredException()
660
661
663 """Check if the request should create an application if an existing
664 application is not found.
665
666 @return: true if an application should be created, false otherwise
667 """
668 if (requestType == RequestType.UIDL) and self.isRepaintAll(request):
669
670
671
672 return True
673
674 elif requestType == RequestType.OTHER:
675
676
677 return True
678
679 return False
680
681
683 """Handles the requested URI. An application can add handlers to do
684 special processing, when a certain URI is requested. The handlers
685 are invoked before any windows URIs are processed and if a
686 DownloadStream is returned it is sent to the client.
687
688 @param stream:
689 the download stream.
690 @param request:
691 the HTTP request instance.
692 @param response:
693 the HTTP response to write to.
694 @raise IOException:
695
696 @see: L{URIHandler}
697 """
698 if stream.getParameter('Location') is not None:
699 self.setStatus(response, 302, 'Found')
700 self.setHeader(response, 'Location',
701 stream.getParameter('Location'))
702 return
703
704
705 data = stream.getStream()
706 if data is not None:
707
708 self.setHeader(response, 'Content-Type', stream.getContentType())
709
710
711 cacheTime = stream.getCacheTime()
712 if cacheTime <= 0:
713 self.setHeader(response, 'Cache-Control', 'no-cache')
714 self.setHeader(response, 'Pragma', 'no-cache')
715 self.setHeader(response, 'Expires', '0')
716 else:
717 self.setHeader(response, 'Cache-Control',
718 'max-age=' + str(cacheTime / 1000))
719 self.setHeader(response, 'Expires',
720 str(1000 * time() + cacheTime))
721
722 self.setHeader(response, 'Pragma', 'cache')
723
724
725
726 names = stream.getParameterNames()
727 if names is not None:
728 for param in names:
729 self.setHeader(response, param, stream.getParameter(param))
730
731
732
733 contentDispositionValue = \
734 stream.getParameter('Content-Disposition')
735 if contentDispositionValue is None:
736 contentDispositionValue = ('filename=\"'
737 + stream.getFileName() + '\"')
738 self.setHeader(response, 'Content-Disposition',
739 contentDispositionValue)
740
741 self.write(response, data.getvalue())
742 data.close()
743
744
746 """Creates a new application and registers it into
747 WebApplicationContext (aka session). This is not meant to be
748 overridden. Override getNewApplication to create the application
749 instance in a custom way.
750
751 @raise ServletException:
752 @raise MalformedURLException:
753 """
754 newApplication = self.getNewApplication(request)
755
756 context = self.getApplicationContext(self.getSession(request))
757 context.addApplication(newApplication)
758
759 return newApplication
760
761
779
780
807
808
809 @classmethod
811 """A helper method to strip away characters that might somehow be
812 used for XSS attacks. Leaves at least alphanumeric characters intact.
813 Also removes eg. ( and ), so values should be safe in javascript too.
814 """
815 sb = StringIO()
816 for c in themeName:
817 if c not in cls._CHAR_BLACKLIST:
818 sb.write(c)
819 result = sb.getvalue()
820 sb.close()
821 return result
822
823
824 _CHAR_BLACKLIST = ['&', '"', '\'', '<', '>', '(', ')', ';']
825
826
827 @classmethod
829 """Returns the default theme. Must never return C{None}.
830 """
831 return cls.DEFAULT_THEME_NAME
832
833
834 - def handleURI(self, applicationManager, window, request, response):
835 """Calls URI handlers for the request. If an URI handler returns a
836 DownloadStream the stream is passed to the client for downloading.
837
838 @return: true if an DownloadStream was sent to the client
839 @raise IOException
840 """
841
842 download = applicationManager.handleURI(window, request, response,
843 self)
844
845
846 if download is not None:
847
848 self.handleDownload(download, request, response)
849 return True
850
851 return False
852
853
882
883
911
912
914 """Creates a new application for the given request.
915
916 @param request:
917 the HTTP request.
918 @return: A new Application instance.
919 @raise ServletException:
920 """
921 raise NotImplementedError
922
923
939
940
942 """Check if this is a request for a static resource and, if it is,
943 serve the resource to the client.
944
945 @return: true if a file was served and the request has been handled,
946 false otherwise.
947 @raise IOException:
948 @raise ServletException:
949 """
950 pathInfo = self.getPathInfo(request)
951
952 if (pathInfo is None) or (len(pathInfo) <= 10):
953 return False
954
955 contextPath = self.getContextPath(request)
956 if (contextPath is not None
957 and self.getRequestUri(request).startswith('/VAADIN/')):
958
959 self.serveStaticResourcesInVAADIN(self.getRequestUri(request),
960 request, response)
961 return True
962
963 elif self.getRequestUri(request).startswith(contextPath + '/VAADIN/'):
964
965 self.serveStaticResourcesInVAADIN(
966 self.getRequestUri(request)[len(contextPath):],
967 request, response)
968 return True
969
970 return False
971
972
974 """Serve resources from VAADIN directory.
975
976 @param filename:
977 The filename to serve. Should always start with /VAADIN/.
978 @param request:
979 @param response:
980 @raise IOException:
981 @raise ServletException:
982 """
983
984 resourceUrl = self.getResource(filename)
985
986 if not exists(resourceUrl):
987
988 msg = 'Requested resource [' + filename + '] not found'
989 logger.info(msg)
990 self.setStatus(response, 404, msg)
991 return
992
993
994
995 if not self.isAllowedVAADINResourceUrl(request, resourceUrl):
996 msg = ('Requested resource [%s] not accessible in the VAADIN '
997 'directory or access to it is forbidden.' % filename)
998 logger.info(msg)
999 self.setStatus(response, 403, msg)
1000 return
1001
1002
1003 lastModifiedTime = 0
1004 try:
1005 lastModifiedTime = int(getmtime(resourceUrl) * 1000)
1006
1007
1008
1009 lastModifiedTime = lastModifiedTime - (lastModifiedTime % 1000)
1010
1011 if self.browserHasNewestVersion(request, lastModifiedTime):
1012 self.setStatus(response, 304, 'Not Modified')
1013 return
1014 except Exception:
1015
1016
1017 logger.info('Failed to find out last modified timestamp. '
1018 + 'Continuing without it.')
1019
1020 mimetype, _ = mimetypes.guess_type(filename)
1021 if mimetype is not None:
1022 self.setHeader(response, 'Content-Type', mimetype)
1023
1024
1025 if lastModifiedTime > 0:
1026 self.setHeader(response, 'Last-Modified', str(lastModifiedTime))
1027
1028
1029
1030
1031
1032
1033
1034
1035 self.setHeader(response, 'Cache-Control', 'max-age: '
1036 + str(self._resourceCacheTime))
1037
1038
1039 fd = open(resourceUrl, 'rb')
1040 self.write(response, fd.read())
1041 fd.close()
1042
1043
1045 """Check whether a URL obtained from a classloader refers to a valid
1046 static resource in the directory VAADIN.
1047
1048 Warning: Overriding of this method is not recommended, but is possible
1049 to support non-default classloaders or servers that may produce URLs
1050 different from the normal ones. The method prototype may change in the
1051 future. Care should be taken not to expose class files or other
1052 resources outside the VAADIN directory if the method is overridden.
1053 """
1054
1055
1056 if ("/VAADIN/" not in self.getUrlPath(resourceUrl)
1057 or "/../" in self.getUrlPath(resourceUrl)):
1058 logger.info("Blocked attempt to access file: " + resourceUrl)
1059 return False
1060
1061
1062
1063 return True
1064
1065
1067 """Checks if the browser has an up to date cached version of
1068 requested resource. Currently the check is performed using the
1069 "If-Modified-Since" header. Could be expanded if needed.
1070
1071 @param request:
1072 The HttpServletRequest from the browser.
1073 @param resourceLastModifiedTimestamp:
1074 The timestamp when the resource was last modified. 0 if
1075 the last modification time is unknown.
1076 @return: true if the If-Modified-Since header tells the cached version
1077 in the browser is up to date, false otherwise
1078 """
1079 if resourceLastModifiedTimestamp < 1:
1080
1081
1082 return False
1083
1084
1085
1086
1087 try:
1088
1089
1090 headerIfModifiedSince = self.getIfModifiedSince(request)
1091 if headerIfModifiedSince >= resourceLastModifiedTimestamp:
1092
1093 return True
1094 except Exception:
1095
1096
1097 pass
1098
1099 return False
1100
1101
1119
1120
1122 path = self.getRequestPathInfo(request)
1123
1124 if (path is not None) and path.startswith('/APP/'):
1125 return True
1126
1127 return False
1128
1129
1131 pathInfo = self.getPathInfo(request)
1132
1133 if (pathInfo is None) or (len(pathInfo) <= 10):
1134 return False
1135
1136 contextPath = self.getContextPath(request)
1137 if ((contextPath is not None)
1138 and self.getRequestUri(request).startswith('/VAADIN/')):
1139 return True
1140
1141 elif self.getRequestUri(request).startswith(contextPath + '/VAADIN/'):
1142 return True
1143
1144 return False
1145
1146
1159
1160
1162 pathInfo = self.getRequestPathInfo(request)
1163
1164 if pathInfo is None:
1165 return False
1166
1167 if pathInfo.startswith('/' + self.UPLOAD_URL_PREFIX):
1168 return True
1169
1170 return False
1171
1172
1176
1177
1189
1190
1192 raise NotImplementedError
1193
1194
1196 """Return the URL from where static files, e.g. the widgetset and
1197 the theme, are served. In a standard configuration the VAADIN folder
1198 inside the returned folder is what is used for widgetsets and themes.
1199
1200 The returned folder is usually the same as the context path and
1201 independent of the application.
1202
1203 @return: The location of static resources (should contain the VAADIN
1204 directory). Never ends with a slash (/).
1205 """
1206
1207
1208 param = self.REQUEST_VAADIN_STATIC_FILE_PATH
1209 staticFileLocation = self.getParameter(request, param)
1210
1211 if staticFileLocation is not None:
1212
1213 return staticFileLocation
1214
1215 return self.getWebApplicationsStaticFileLocation(request)
1216
1217
1219 """The default method to fetch static files location (URL). This
1220 method does not check for request attribute
1221 C{REQUEST_VAADIN_STATIC_FILE_PATH}.
1222 """
1223
1224 staticFileLocation = self.getApplicationOrSystemProperty(
1225 self.PARAMETER_VAADIN_RESOURCES, None)
1226
1227 if staticFileLocation is not None:
1228 return staticFileLocation
1229
1230
1231
1232
1233
1234 ctxPath = self.getContextPath(request)
1235
1236
1237
1238 if ((len(ctxPath) == 0)
1239 and (self.originalContextPath(request) is not None)):
1240
1241
1242 ctxPath = self.originalContextPath(request)
1243
1244
1245 ctxPath = self.removeHeadingOrTrailing(ctxPath, '/')
1246
1247 if ctxPath == '':
1248 return ''
1249 else:
1250 return '/' + ctxPath
1251
1252
1253 @classmethod
1255 """Remove any heading or trailing "what" from the "string".
1256 """
1257 while string.startswith(what):
1258 string = string[1:]
1259
1260 while string.endswith(what):
1261 string = string[:-1]
1262
1263 return string
1264
1265
1267 """Write a redirect response to the main page of the application.
1268
1269 @raise IOException:
1270 if sending the redirect fails due to an input/output
1271 error or a bad application URL
1272 """
1273 applicationUrl = self.getApplicationUrl(request)
1274 self.sendRedirect(response, applicationUrl)
1275
1276
1277 - def writeAjaxPage(self, request, response, window, application):
1278 """This method writes the html host page (aka kickstart page) that
1279 starts the actual Muntjac application.
1280
1281 If one needs to override parts of the host page, it is suggested
1282 that one overrides on of several submethods which are called by
1283 this method:
1284
1285 - L{setAjaxPageHeaders}
1286 - L{writeAjaxPageHtmlHeadStart}
1287 - L{writeAjaxPageHtmlHeader}
1288 - L{writeAjaxPageHtmlBodyStart}
1289 - L{writeAjaxPageHtmlMuntjacScripts}
1290 - L{writeAjaxPageHtmlMainDiv}
1291 - L{writeAjaxPageHtmlBodyEnd}
1292
1293 @param request:
1294 the HTTP request.
1295 @param response:
1296 the HTTP response to write to.
1297 @param window:
1298 @param application:
1299 @raise IOException:
1300 if the writing failed due to input/output error.
1301 @raise MalformedURLException:
1302 if the application is denied access the persistent data
1303 store represented by the given URL.
1304 """
1305
1306 fragment = self.getParameter(request, self.REQUEST_FRAGMENT,
1307 None) is not None
1308
1309 if fragment:
1310
1311
1312 self.setParameter(request, clsname(Application), application)
1313
1314 page = StringIO()
1315
1316 if window.getCaption() is None:
1317 title = 'Muntjac 6'
1318 else:
1319 title = window.getCaption()
1320
1321
1322
1323
1324 appUrl = self.getUrlPath( self.getApplicationUrl(request) )
1325 if appUrl.endswith('/'):
1326 appUrl = appUrl[:-1]
1327
1328 themeName = self.getThemeForWindow(request, window)
1329
1330 themeUri = self.getThemeUri(themeName, request)
1331
1332 if not fragment:
1333 self.setAjaxPageHeaders(response)
1334 self.writeAjaxPageHtmlHeadStart(page, request)
1335 self.writeAjaxPageHtmlHeader(page, title, themeUri, request)
1336 self.writeAjaxPageHtmlBodyStart(page, request)
1337
1338 appId = appUrl
1339 if '' == appUrl:
1340 appId = 'ROOT'
1341
1342 appId = re.sub('[^a-zA-Z0-9]', '', appId)
1343
1344
1345
1346 hashCode = hash(appId)
1347 if hashCode < 0:
1348 hashCode = -hashCode
1349
1350 appId = appId + '-' + str(hashCode)
1351
1352 self.writeAjaxPageHtmlMuntjacScripts(window, themeName, application,
1353 page, appUrl, themeUri, appId, request)
1354
1355
1356
1357
1358
1359
1360
1361 appClass = 'v-app-' + self.getApplicationCSSClassName()
1362
1363 themeClass = ''
1364 if themeName is not None:
1365 themeClass = 'v-theme-' + re.sub('[^a-zA-Z0-9]', '', themeName)
1366 else:
1367 themeClass = ('v-theme-'
1368 + re.sub('[^a-zA-Z0-9]', '', self.getDefaultTheme()))
1369
1370 classNames = 'v-app ' + themeClass + ' ' + appClass
1371
1372 divStyle = None
1373 if self.getParameter(request, self.REQUEST_APPSTYLE, None) is not None:
1374 divStyle = ('style=\"'
1375 + self.getParameter(request, self.REQUEST_APPSTYLE) + '\"')
1376
1377 self.writeAjaxPageHtmlMainDiv(page, appId, classNames, divStyle,
1378 request)
1379
1380 if not fragment:
1381 page.write('</body>\n</html>\n')
1382
1383 self.write(response, page.getvalue())
1384 page.close()
1385
1386
1388 """Returns the application class identifier for use in the
1389 application CSS class name in the root DIV. The application
1390 CSS class name is of form "v-app-"+getApplicationCSSClassName().
1391
1392 This method should normally not be overridden.
1393
1394 @return: The CSS class name to use in combination with "v-app-".
1395 """
1396 try:
1397 return self.getApplicationClass().__name__
1398 except Exception, e:
1399 logger.warning('getApplicationCSSClassName failed')
1400 return 'unknown'
1401
1402
1423
1424
1425 - def writeAjaxPageHtmlMainDiv(self, page, appId, classNames, divStyle,
1426 request):
1427 """Method to write the div element into which that actual Muntjac
1428 application is rendered.
1429
1430 Override this method if you want to add some custom html around around
1431 the div element into which the actual Muntjac application will be
1432 rendered.
1433
1434 @raise IOException:
1435 """
1436 page.write('<div id=\"' + appId + '\" class=\"' + classNames + '\" ' \
1437 + (divStyle if divStyle is not None else '') + '>')
1438 page.write('<div class=\"v-app-loading\"></div>')
1439 page.write('</div>\n')
1440 page.write('<noscript>' + self.getNoScriptMessage() + '</noscript>')
1441
1442
1443 - def writeAjaxPageHtmlMuntjacScripts(self, window, themeName, application,
1444 page, appUrl, themeUri, appId, request):
1445 """Method to write the script part of the page which loads needed
1446 Muntjac scripts and themes.
1447
1448 Override this method if you want to add some custom html around
1449 scripts.
1450
1451 @raise ServletException:
1452 @raise IOException:
1453 """
1454
1455 requestWidgetset = self.getParameter(request, self.REQUEST_WIDGETSET,
1456 None)
1457 sharedWidgetset = self.getParameter(request,
1458 self.REQUEST_SHARED_WIDGETSET, None)
1459
1460 if requestWidgetset is None and sharedWidgetset is None:
1461
1462
1463
1464 requestWidgetset = self.getApplicationOrSystemProperty(
1465 self.PARAMETER_WIDGETSET, self.DEFAULT_WIDGETSET)
1466
1467 if requestWidgetset is not None:
1468 widgetset = requestWidgetset
1469 widgetsetBasePath = \
1470 self.getWebApplicationsStaticFileLocation(request)
1471 else:
1472 widgetset = sharedWidgetset
1473 widgetsetBasePath = self.getStaticFilesLocation(request)
1474
1475 widgetset = self.stripSpecialChars(widgetset)
1476 widgetsetFilePath = (widgetsetBasePath
1477 + '/' + self.WIDGETSET_DIRECTORY_PATH + widgetset
1478 + '/' + widgetset + '.nocache.js?'
1479 + str( int(time() * 1000) ))
1480
1481
1482 systemMessages = None
1483
1484 try:
1485 systemMessages = self.getSystemMessages()
1486 except SystemMessageException, e:
1487 raise ServletException('CommunicationError!', e)
1488
1489 page.write('<script type=\"text/javascript\">\n')
1490 page.write('//<![CDATA[\n')
1491 page.write(('if(!vaadin || !vaadin.vaadinConfigurations) {\n '
1492 + 'if(!vaadin) { var vaadin = {}} \n'
1493 + 'vaadin.vaadinConfigurations = {};\n'
1494 + 'if (!vaadin.themesLoaded) '
1495 + '{ vaadin.themesLoaded = {}; }\n'))
1496
1497 if not self.isProductionMode():
1498 page.write('vaadin.debug = true;\n')
1499
1500 page.write(('document.write(\'<iframe tabIndex=\"-1\" '
1501 + 'id=\"__gwt_historyFrame\" '
1502 + 'style=\"position:absolute;width:0;height:0;border:0;'
1503 + 'overflow:hidden;\" '
1504 + 'src=\"javascript:false\"></iframe>\');\n'))
1505
1506 page.write('document.write(\"<script language=\'javascript\' '
1507 + 'src=\''
1508 + widgetsetFilePath + '\'><\\/script>\");\n}\n')
1509
1510 page.write('vaadin.vaadinConfigurations[\"'
1511 + appId + '\"] = {')
1512
1513 page.write('appUri:\'' + appUrl + '\', ')
1514
1515 if window != application.getMainWindow():
1516 page.write('windowName: \"' \
1517 + JsonPaintTarget.escapeJSON(window.getName()) + '\", ')
1518
1519 if self.isStandalone():
1520 page.write('standalone: true, ')
1521
1522 page.write('themeUri:')
1523 page.write('\"' + themeUri + '\"' if themeUri is not None else 'null')
1524 page.write(', versionInfo : {vaadinVersion:\"')
1525 page.write(self.VERSION)
1526 page.write('\",applicationVersion:\"')
1527 page.write(JsonPaintTarget.escapeJSON(application.getVersion()))
1528 page.write('\"}')
1529
1530 if systemMessages is not None:
1531
1532 caption = systemMessages.getCommunicationErrorCaption()
1533 if caption is not None:
1534 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"'
1535
1536 message = systemMessages.getCommunicationErrorMessage()
1537 if message is not None:
1538 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"'
1539
1540 url = systemMessages.getCommunicationErrorURL()
1541 if url is not None:
1542 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"'
1543 else:
1544 url = 'null'
1545
1546 page.write(',\"comErrMsg\": {' + '\"caption\":'
1547 + caption + ',' + '\"message\" : ' + message + ','
1548 + '\"url\" : ' + url + '}')
1549
1550
1551 caption = systemMessages.getAuthenticationErrorCaption()
1552 if caption is not None:
1553 caption = '\"' + JsonPaintTarget.escapeJSON(caption) + '\"'
1554
1555 message = systemMessages.getAuthenticationErrorMessage()
1556 if message is not None:
1557 message = '\"' + JsonPaintTarget.escapeJSON(message) + '\"'
1558
1559 url = systemMessages.getAuthenticationErrorURL()
1560 if url is not None:
1561 url = '\"' + JsonPaintTarget.escapeJSON(url) + '\"'
1562 else:
1563 url = 'null'
1564
1565 page.write(',\"authErrMsg\": {' + '\"caption\":'
1566 + caption + ',' + '\"message\" : ' + message
1567 + ',' + '\"url\" : ' + url + '}')
1568
1569 page.write('};\n//]]>\n</script>\n')
1570
1571 if themeName is not None:
1572
1573
1574
1575 page.write('<script type=\"text/javascript\">\n')
1576 page.write('//<![CDATA[\n')
1577 page.write('if(!vaadin.themesLoaded[\'' + themeName + '\']) {\n')
1578 page.write('var stylesheet = document.createElement(\'link\');\n')
1579 page.write('stylesheet.setAttribute(\'rel\', \'stylesheet\');\n')
1580 page.write('stylesheet.setAttribute(\'type\', \'text/css\');\n')
1581 page.write('stylesheet.setAttribute(\'href\', \'' \
1582 + themeUri + '/styles.css\');\n')
1583 page.write('document.getElementsByTagName(\'head\')[0]'
1584 '.appendChild(stylesheet);\n')
1585 page.write('vaadin.themesLoaded[\'' \
1586 + themeName + '\'] = true;\n}\n')
1587 page.write('//]]>\n</script>\n')
1588
1589
1590
1591 page.write('<script type=\"text/javascript\">\n')
1592 page.write('//<![CDATA[\n')
1593 page.write('setTimeout(\'if (typeof '
1594 + widgetset.replace('.', '_')
1595 + ' == \"undefined\") {alert(\"Failed to load the widgetset: '
1596 + widgetsetFilePath
1597 + '\")};\',15000);\n'
1598 + '//]]>\n</script>\n')
1599
1600
1602 """@return: true if the served application is considered to be the
1603 only or main content of the host page. E.g. various embedding
1604 solutions should override this to false.
1605 """
1606 return True
1607
1608
1609 - def writeAjaxPageHtmlBodyStart(self, page, request):
1610 """Method to open the body tag of the html kickstart page.
1611
1612 This method is responsible for closing the head tag and opening
1613 the body tag.
1614
1615 Override this method if you want to add some custom html to the page.
1616
1617 @raise IOException:
1618 """
1619 page.write('\n</head>\n<body scroll=\"auto\" class=\"'
1620 + ApplicationConnection.GENERATED_BODY_CLASSNAME
1621 + '\">\n')
1622
1623
1625 """Method to write the contents of head element in html kickstart page.
1626
1627 Override this method if you want to add some custom html to the header
1628 of the page.
1629
1630 @raise IOException:
1631 """
1632 page.write('<meta http-equiv=\"Content-Type\" '
1633 + 'content=\"text/html; charset=utf-8\"/>\n')
1634
1635 context = self.getApplicationContext(self.getSession(request))
1636 browser = context.getBrowser()
1637 if browser.isIE():
1638
1639
1640 page.write('<meta http-equiv=\"X-UA-Compatible\" '
1641 + 'content=\"chrome=1\"/>\n')
1642
1643 page.write('<style type=\"text/css\">'
1644 + 'html, body {height:100%;margin:0;}</style>')
1645
1646
1647 page.write('<link rel=\"shortcut icon\" '
1648 + 'type=\"image/vnd.microsoft.icon\" href=\"'
1649 + themeUri
1650 + '/favicon.ico\" />')
1651 page.write('<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" '
1652 + 'href=\"' + themeUri + '/favicon.ico\" />')
1653 page.write('<title>'
1654 + self.safeEscapeForHtml(title)
1655 + '</title>')
1656
1657
1658 - def writeAjaxPageHtmlHeadStart(self, page, request):
1659 """Method to write the beginning of the html page.
1660
1661 This method is responsible for writing appropriate doc type
1662 declarations and to open html and head tags.
1663
1664 Override this method if you want to add some custom html to the
1665 very beginning of the page.
1666
1667 @raise IOException:
1668 """
1669
1670 page.write('<!DOCTYPE html PUBLIC \"-//W3C//DTD '
1671 + 'XHTML 1.0 Transitional//EN\" '
1672 + '\"http://www.w3.org/TR/xhtml1/'
1673 + 'DTD/xhtml1-transitional.dtd\">\n')
1674 page.write('<html xmlns=\"http://www.w3.org/1999/xhtml\"'
1675 + '>\n<head>\n')
1676
1677
1679 """Method to set http request headers for the Muntjac kickstart page.
1680
1681 Override this method if you need to customize http headers of the page.
1682 """
1683
1684 self.setHeader(response, 'Cache-Control', 'no-cache')
1685 self.setHeader(response, 'Pragma', 'no-cache')
1686 self.setHeader(response, 'Expires', '0')
1687 self.setHeader(response, 'Content-Type', 'text/html; charset=UTF-8')
1688
1689
1691 """Returns a message printed for browsers without scripting support
1692 or if browsers scripting support is disabled.
1693 """
1694 return ('You have to enable javascript in your browser to use an '
1695 'application built with Muntjac.')
1696
1697
1699 """Gets the current application URL from request.
1700
1701 @param request:
1702 the HTTP request.
1703 @raise MalformedURLException:
1704 if the application is denied access to the persistent
1705 data store represented by the given URL.
1706 """
1707 reqURL = 'https://' if self.isSecure(request) else 'http://'
1708 reqHost = request.environ().get('HTTP_HOST')
1709 if reqHost:
1710 reqURL += reqHost
1711 else:
1712 reqURL += self.getServerName(request)
1713 if (self.isSecure(request) and self.getServerPort(request) == 443
1714 or (not self.isSecure(request)
1715 and self.getServerPort(request) == 80)):
1716 pass
1717 else:
1718 reqURL += ':%d' % self.getServerPort(request)
1719 reqURL += self.getRequestUri(request)
1720
1721
1722 if self.getParameter(request, 'javax.servlet.include.servlet_path',
1723 None) is not None:
1724
1725 servletPath = (self.getParameter(request,
1726 'javax.servlet.include.context_path', None)
1727 + self.getParameter(request,
1728 'javax.servlet.include.servlet_path', None))
1729
1730
1731 else:
1732 servletPath = (self.getContextPath(request)
1733 + self.getServletPath(request))
1734
1735 if len(servletPath) == 0 or servletPath[len(servletPath) - 1] != '/':
1736 servletPath = servletPath + '/'
1737
1738 return urljoin(reqURL, servletPath)
1739
1740
1742 """Gets the existing application for given request. Looks for
1743 application instance for given request based on the requested URL.
1744
1745 @param request:
1746 the HTTP request.
1747 @param allowSessionCreation:
1748 true if a session should be created if no session
1749 exists, false if no session should be created
1750 @return: Application instance, or null if the URL does not map to
1751 valid application.
1752 @raise MalformedURLException:
1753 if the application is denied access to the persistent
1754 data store represented by the given URL.
1755 @raise SessionExpiredException:
1756 """
1757
1758 session = self.getSession(request, allowSessionCreation)
1759
1760 if session is None:
1761 raise SessionExpiredException()
1762
1763 context = self.getApplicationContext(session)
1764
1765
1766 applications = context.getApplications()
1767
1768
1769 for sessionApplication in applications:
1770 sessionApplicationPath = \
1771 self.getUrlPath(sessionApplication.getURL())
1772 requestApplicationPath = \
1773 self.getUrlPath(self.getApplicationUrl(request))
1774
1775 if requestApplicationPath == sessionApplicationPath:
1776
1777 if sessionApplication.isRunning():
1778 return sessionApplication
1779
1780
1781
1782 self.getApplicationContext(
1783 session).removeApplication(sessionApplication)
1784 break
1785
1786
1787 return None
1788
1789
1791 """Ends the application.
1792
1793 @param request:
1794 the HTTP request.
1795 @param response:
1796 the HTTP response to write to.
1797 @param application:
1798 the application to end.
1799 @raise IOException:
1800 if the writing failed due to input/output error.
1801 """
1802 logoutUrl = application.getLogoutURL()
1803
1804 if logoutUrl is None:
1805 logoutUrl = application.getURL()
1806
1807 session = self.getSession(request)
1808 if session is not None:
1809 self.getApplicationContext(session).removeApplication(application)
1810
1811 response.sendRedirect(logoutUrl)
1812
1813
1815 """Gets the existing application or create a new one. Get a
1816 window within an application based on the requested URI.
1817
1818 @param request:
1819 the HTTP Request.
1820 @param application:
1821 the Application to query for window.
1822 @return: Window matching the given URI or null if not found.
1823 @raise ServletException:
1824 if an exception has occurred that interferes with the
1825 servlet's normal operation.
1826 """
1827
1828 assumedWindow = None
1829 path = self.getRequestPathInfo(request)
1830
1831
1832 if not (path is None or len(path) == 0 or path == '/'):
1833 if path.startswith('/APP/'):
1834
1835 return application.getMainWindow()
1836
1837 windowName = None
1838 if path[0] == '/':
1839 path = path[1:]
1840
1841 index = path.find('/')
1842
1843 if index < 0:
1844 windowName = path
1845 path = ''
1846 else:
1847 windowName = path[:index]
1848
1849 assumedWindow = application.getWindow(windowName)
1850
1851 return applicationManager.getApplicationWindow(request, self,
1852 application, assumedWindow)
1853
1854
1856 """Returns the path info; note that this _can_ be different than
1857 request.getPathInfo(). Examples where this might be useful:
1858
1859 - An application runner servlet that runs different Muntjac
1860 applications based on an identifier.
1861 - Providing a REST interface in the context root, while serving a
1862 Muntjac UI on a sub-URI using only one servlet (e.g. REST on
1863 http://example.com/foo, UI on http://example.com/foo/vaadin)
1864 """
1865 return self.getPathInfo(request)
1866
1867
1869 """Gets relative location of a theme resource.
1870
1871 @param theme:
1872 the Theme name.
1873 @param resource:
1874 the Theme resource.
1875 @return: External URI specifying the resource
1876 """
1877 if self._resourcePath is None:
1878 return resource.getResourceId()
1879
1880 return self._resourcePath + theme + '/' + resource.getResourceId()
1881
1882
1888
1889
1897
1898
1899 - def getApplicationContext(self, session):
1900 """Gets the application context from an HttpSession. If no context
1901 is currently stored in a session a new context is created and stored
1902 in the session.
1903
1904 @param session:
1905 the HTTP session.
1906 @return: the application context for HttpSession.
1907 """
1908
1909
1910
1911
1912 return WebApplicationContext.getApplicationContext(session, self)
1913
1914
1916 """Override this method if you need to use a specialized
1917 communication mananger implementation.
1918
1919 @deprecated: Instead of overriding this method, override
1920 L{WebApplicationContext} implementation via
1921 L{getApplicationContext} method and in that customized
1922 implementation return your CommunicationManager in
1923 L{WebApplicationContext.getApplicationManager}
1924 method.
1925 """
1926 warn("deprecated", DeprecationWarning)
1927
1928 from muntjac.terminal.gwt.server.communication_manager import \
1929 CommunicationManager
1930
1931 return CommunicationManager(application)
1932
1933
1934 @classmethod
1936 """Escapes characters to html entities. An exception is made for some
1937 "safe characters" to keep the text somewhat readable.
1938
1939 @return: a safe string to be added inside an html tag
1940 """
1941 if unsafe is None:
1942 return None
1943
1944 safe = StringIO()
1945 for c in unsafe:
1946 if cls.isSafe(ord(c)):
1947 safe.write(c)
1948 else:
1949 safe.write('&#')
1950 safe.write(ord(c))
1951 safe.write(';')
1952
1953 result = safe.getvalue()
1954 safe.close()
1955
1956 return result
1957
1958
1959 @classmethod
1961
1962 return ((c > 47 and c < 58)
1963 or (c > 64 and c < 91)
1964 or (c > 96 and c < 123))
1965
1968 """Implementation of IErrorEvent interface."""
1969
1971 self._owner = owner
1972 self._throwable = throwable
1973
1974
1976 """Gets the contained throwable.
1977
1978 @see: L{muntjac.terminal.terminal.IErrorEvent.getThrowable()}
1979 """
1980 return self._throwable
1981
1982
1984 """Gets the source ParameterHandler.
1985
1986 @see: L{IErrorEvent.getParameterHandler}
1987 """
1988 return self._owner
1989
1992 """Implementation of URIHandler.IErrorEvent interface."""
1993
1995 self._owner = owner
1996 self._throwable = throwable
1997
1998
2000 """Gets the contained throwable.
2001
2002 @see: L{muntjac.terminal.terminal.IErrorEvent.getThrowable()}
2003 """
2004 return self._throwable
2005
2006
2008 """Gets the source URIHandler.
2009
2010 @see: L{muntjac.terminal.uri_handler.IErrorEvent.getURIHandler}
2011 """
2012 return self._owner
2013
2016
2018 self._throwable = throwable
2019
2020
2022 return self._throwable
2023