Package gavo :: Package web :: Module common
[frames] | no frames]

Source Code for Module gavo.web.common

  1  """ 
  2  Common code for the nevow based interface. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import re 
 12  import urllib 
 13   
 14  from nevow import appserver 
 15  from nevow import tags as T 
 16  from nevow import loaders 
 17  from nevow import inevow 
 18  from nevow import rend 
 19  from nevow import static 
 20  try: 
 21      from twisted.web import http 
 22  except ImportError: 
 23      from twisted.protocols import http #noflake: conditional import 
 24   
 25  from gavo import base 
 26  from gavo import svcs 
 27  from gavo import utils 
 28  from gavo.base import meta 
 29  from gavo.protocols import creds 
 30   
 31   
 32  # monkeypatching static's mime magic 
 33  static.File.contentTypes[".ascii"] = "application/octet-stream" 
 34  static.File.contentTypes[".f"] = "text/x-fortran" 
 35  static.File.contentTypes[".vot"] = base.votableType, 
 36  static.File.contentTypes[".rd"] = "application/x-gavo-descriptor+xml" 
 37  static.File.contentTypes[".f90"] = "text/x-fortran" 
 38  static.File.contentTypes[".skyglow"] = "text/plain" 
 39  static.File.contentTypes[".fitstable"] = "application/fits" 
 40  static.File.contentTypes[".fits"] = "image/fits" 
 41  # this one is for co-operation with ifpages 
 42  static.File.contentTypes[".shtml"] = "text/nevow-template" 
 43   
 44   
45 -def escapeForHTML(aString):
46 return aString.replace("&", "&amp;" 47 ).replace("<", "&lt;" 48 ).replace(">", "&gt;")
49 50
51 -def getfirst(ctx, key, default):
52 """returns the first value of key in the nevow context ctx. 53 """ 54 return utils.getfirst(inevow.IRequest(ctx).args, key, default)
55 56
57 -class HTMLMetaBuilder(meta.MetaBuilder):
58 - def __init__(self, macroPackage=None):
59 meta.MetaBuilder.__init__(self) 60 self.resultTree, self.currentAtom = [[]], None 61 self.macroPackage = macroPackage
62
63 - def startKey(self, atom):
64 self.resultTree.append([])
65
66 - def enterValue(self, value):
67 val = value.getContent("html", self.macroPackage) 68 if val: 69 self.resultTree[-1].append(T.xml(val)) 70 # for meta items rendering their children themselves (which return 71 # in IncludesChildren object), do not fold in rendered children 72 # (the None sentinel is handed in endKey) 73 if isinstance(val, meta.IncludesChildren): 74 self.resultTree[-1].append(None)
75
76 - def _isCompound(self, childItem):
77 """should return true some day for compound meta items that should 78 be grouped in some way. 79 """ 80 return False
81
82 - def endKey(self, atom):
83 children = [c for c in self.resultTree.pop() if c] 84 # see enterValue on why we're doing this 85 if (self.resultTree[-1] 86 and self.resultTree[-1][-1] is None): 87 return 88 89 if len(children)>1: 90 childElements = [] 91 for c in children: 92 if self._isCompound(c)>1: 93 class_ = "compoundMetaItem" 94 else: 95 class_ = "metaItem" 96 childElements.append(T.li(class_=class_)[c]) 97 self.resultTree[-1].append(T.ul(class_="metaEnum")[childElements]) 98 elif len(children)==1: 99 self.resultTree[-1].append(children[0])
100
101 - def getResult(self):
102 return self.resultTree[0]
103
104 - def clear(self):
105 self.resultTree = [[]]
106 107
108 -def runAuthenticated(ctx, reqGroup, fun, *args):
109 """returns the value of fun(*args) if the logged in user is in reqGroup, 110 requests authentication otherwise. 111 """ 112 request = inevow.IRequest(ctx) 113 if creds.hasCredentials(request.getUser(), request.getPassword(), reqGroup): 114 return fun(*args) 115 else: 116 raise svcs.Authenticate()
117 118
119 -class doctypedStan(loaders.stan):
120 """is the stan loader with a doctype and a namespace added. 121 """ 122 123 DOCTYPE = T.xml('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' 124 ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n') 125
126 - def __init__(self, rootEl, pattern=None):
127 super(doctypedStan, self).__init__(T.invisible[self.DOCTYPE, 128 rootEl(xmlns="http://www.w3.org/1999/xhtml")], pattern)
129 130 131 JSEXT = ".js" 132 133
134 -class CommonRenderers(object):
135 """A base for renderer (python) mixins within the DC. 136 137 Including standard stylesheets/js/whatever: 138 <head n:render="commonhead">...</head> 139 140 Rendering internal links (important for off-root operation): 141 142 * <tag href|src="/foo" n:render="rootlink"/> 143 144 """
145 - def render_unicode(self, ctx, data):
146 """returns unicode(data); use this when render="string" might have to 147 format non-ASCII. 148 """ 149 return ctx.tag[unicode(data)]
150 156 munge("src") 157 munge("href") 158 return tag
159
160 - def render_commonhead(self, ctx, data):
161 # we do not want to blindly append these things to the tag since user- 162 # provided items in the header should have access to this environment, 163 # in particular to jquery; thus, this stuff must be as early as possible. 164 originalChildren = ctx.tag.children[:] 165 ctx.tag.clear() 166 res = ctx.tag[ 167 T.meta(**{"http-equiv": "Content-type", 168 "content": "text/html;charset=UTF-8"}), 169 T.link(rel="stylesheet", href=base.makeSitePath("/formal.css"), 170 type="text/css"), 171 T.link(rel="stylesheet", href=base.makeSitePath( 172 "/static/css/gavo_dc.css"), type="text/css"), 173 T.script(src=base.makeSitePath("/static/js/jquery-gavo.js"), 174 type="text/javascript"), 175 T.script(type='text/javascript', src=base.makeSitePath( 176 'static/js/formal'+JSEXT)), 177 T.script(src=base.makeSitePath("/static/js/gavo"+JSEXT), 178 type="text/javascript"), 179 originalChildren, 180 ] 181 if base.getConfig("web", "operatorCSS"): 182 res[ 183 T.link(rel="stylesheet", type="text/css", 184 href=base.getConfig("web", "operatorCSS"))] 185 return res
186
187 - def render_urlescape(self, ctx, data):
188 """renders data as a url-escaped string. 189 """ 190 if isinstance(data, unicode): 191 data = data.encode("utf-8") 192 return urllib.quote(data)
193
194 - def render_getconfig(self, ctx, data):
195 """looks up the text child in the DaCHS configuration and inserts 196 the value as a (unicode) string. 197 198 The config key is either [section]item or just item for something 199 in [general]. Behaviour for undefined config items is undefined. 200 """ 201 configKey = ctx.tag.children[0].strip() 202 mat = re.match("\[([^]]*)\](.*)", configKey) 203 if mat: 204 content = base.getConfig(mat.group(1), mat.group(2)) 205 else: 206 content = base.getConfig(configKey) 207 ctx.tag.clear() 208 return ctx.tag[content]
209 210
211 -class _UploadLimiter(object):
212 """A wrapper class for upload destinations that limits writes to 213 limit bytes and sends a 413 if the limit is reached. 214 """ 215 message = (b"If you receive this error using sync, try again\r\n" 216 b"with async (the limits are more generous there). If you\r\n" 217 b"receive this message with async, see if the upload can be\r\n" 218 b"reduced by transmitting only columns absolutely necessary\r\n") 219
220 - def __init__(self, original, request, limit):
221 self.original, self.request, self.limit = original, request, limit 222 self.writtenSoFar = 0 223 224 # at least twisted 16 doesn't have clientproto while doing the 225 # upload; I'll fix this here heuristically 226 if request.clientproto.startswith("("): 227 request.clientproto = getattr(request.channel, "_version", "HTTP/1.0") 228 # ...about the same for the client attribute 229 if not hasattr(request, "client"): 230 request.client = getattr(request.channel, "client", "(unknown client)")
231
232 - def __getattr__(self, attrName):
233 return getattr(self.original, attrName)
234
235 - def write(self, bytes):
236 self.writtenSoFar += len(bytes) 237 if self.writtenSoFar>=self.limit: 238 self.request.setResponseCode(413, "Request Entity Too Large") 239 self.request.setHeader("content-length", str(len(self.message))) 240 self.request.setHeader("content-type", "text/plain") 241 self.request.write(self.message) 242 self.request.finishRequest(0) 243 244 else: 245 self.original.write(bytes)
246 247
248 -class Request(appserver.NevowRequest):
249 """a custom request class used in DaCHS' application server. 250 251 The main change is that we enforce a limit to the size of the payload. 252 This is especially crucial because nevow blocks while parsing the 253 header payload. 254 """
255 - def gotLength(self, length):
256 appserver.NevowRequest.gotLength(self, length) 257 258 # we'd like to distinguish between sync and async here, but 259 # the request is largely unpopulated here. As far as I 260 # can see, the only thing I can do is look at a piece of 261 # private twisted API 262 isSync = True 263 try: 264 path = self.channel._path.split("?")[0] 265 isSync = path.endswith("/sync") 266 except Exception as msg: 267 base.ui.notifyError("In gotLength: %s"%msg) 268 269 if isSync: 270 maxUploadSize = base.getConfig("web", "maxSyncUploadSize") 271 else: 272 maxUploadSize = base.getConfig("web", "maxUploadSize") 273 274 # The following is a shortcut to break out as soon as possible 275 # when the uploading program declares a content length (though 276 # by twisted's architecture we can't exit in this place anyway) 277 if length and length>maxUploadSize: 278 maxUploadSize = 0 279 280 if self.content: 281 self.content = _UploadLimiter(self.content, self, maxUploadSize)
282
283 - def connectionLost(self, reason):
284 # standard twisted (at least as of 17.5) doesn't log these, so 285 # we try do it manually. 286 287 # helper to still let us log when request is damaged. 288 if not hasattr(self, "client"): 289 self.client = None 290 291 try: 292 self.channel.factory.log(self) 293 except Exception as exc: 294 base.ui.notifyError("Request with lost connection wasn't logged: %s"% 295 exc) 296 return appserver.NevowRequest.connectionLost(self, reason)
297 298
299 -class TypedData(rend.Page):
300 """A simple resource just producing bytes passed in during construction, 301 declared as a special content type. 302 """
303 - def __init__(self, data, mime):
304 rend.Page.__init__(self) 305 self.data, self.mime = data, mime
306
307 - def renderHTTP(self, ctx):
308 request = inevow.IRequest(ctx) 309 request.setHeader("content-type", self.mime) 310 request.write(self.data) 311 return ""
312