예제 #1
0
    def actionExpand(self, request, tzid):
        """
        Expand a timezone within specified start/end dates.
        """

        if set(request.args.keys()) - set(("start", "end", "changedsince",)):
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        start = request.args.get("start", ())
        if len(start) == 0:
            self.problemReport("invalid-start", "Missing start request-URI query parameter", responsecode.BAD_REQUEST)
        if len(start) > 1:
            self.problemReport("invalid-start", "Too many start request-URI query parameters", responsecode.BAD_REQUEST)
        elif len(start) == 1:
            try:
                if len(start[0]) != 20:
                    raise ValueError()
                start = DateTime.parseText(start[0], fullISO=True)
            except ValueError:
                self.problemReport("invalid-start", "Invalid start request-URI query parameter value", responsecode.BAD_REQUEST)

        end = request.args.get("end", ())
        if len(end) == 0:
            self.problemReport("invalid-end", "Missing end request-URI query parameter", responsecode.BAD_REQUEST)
        if len(end) > 1:
            self.problemReport("invalid-end", "Too many end request-URI query parameters", responsecode.BAD_REQUEST)
        elif len(end) == 1:
            try:
                if len(end[0]) != 20:
                    raise ValueError()
                end = DateTime.parseText(end[0], fullISO=True)
            except ValueError:
                self.problemReport("invalid-end", "Invalid end request-URI query parameter value", responsecode.BAD_REQUEST)
            if end <= start:
                self.problemReport("invalid-end", "Invalid end request-URI query parameter value - earlier than start", responsecode.BAD_REQUEST)

        tzdata = self.timezones.getTimezone(tzid)
        if tzdata is None:
            self.problemReport("tzid-not-found", "Time zone identifier not found", responsecode.NOT_FOUND)

        # Now do the expansion (but use a cache to avoid re-calculating TZs)
        observances = self.expandcache.get((tzid, start, end), None)
        if observances is None:
            observances = tzexpandlocal(tzdata, start, end, utc_onset=True)
            self.expandcache[(tzid, start, end)] = observances

        # Turn into JSON
        result = {
            "dtstamp": self.timezones.dtstamp,
            "tzid": tzid,
            "observances": [
                {
                    "name": name,
                    "onset": onset.getXMLText(),
                    "utc-offset-from": utc_offset_from,
                    "utc-offset-to": utc_offset_to,
                } for onset, utc_offset_from, utc_offset_to, name in observances
            ],
        }
        return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
예제 #2
0
    def actionList(self, request):
        """
        Return a list of all timezones known to the server.
        """

        if set(request.args.keys()) - set(("changedsince",)):
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        changedsince = request.args.get("changedsince", ())
        if len(changedsince) > 1:
            self.problemReport("invalid-changedsince", "Too many changedsince request-URI query parameters", responsecode.BAD_REQUEST)
        if len(changedsince) == 1:
            # Validate a date-time stamp
            changedsince = changedsince[0]
            try:
                dt = DateTime.parseText(changedsince, fullISO=True)
            except ValueError:
                self.problemReport("invalid-changedsince", "Invalid changedsince request-URI query parameter value", responsecode.BAD_REQUEST)
            if not dt.utc():
                self.problemReport("invalid-changedsince", "Invalid changedsince request-URI query parameter value - not UTC", responsecode.BAD_REQUEST)

        timezones = []
        for tz in self.timezones.listTimezones(changedsince):
            timezones.append({
                "tzid": tz.tzid,
                "last-modified": tz.dtstamp,
                "aliases": tz.aliases,
            })
        result = {
            "dtstamp": self.timezones.dtstamp,
            "timezones": timezones,
        }
        return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
예제 #3
0
 def _error(self, status, description):
     raise HTTPError(JSONResponse(
         responsecode.BAD_REQUEST,
         {
             "status": status,
             "description": description,
         },
     ))
예제 #4
0
 def _ok(self, status, description, result=None):
     if result is None:
         result = {}
     result["status"] = status
     result["description"] = description
     return JSONResponse(
         responsecode.OK,
         result,
     )
예제 #5
0
    def _processRequest(self):
        """
        Process the request by sending it to the relevant server.

        @return: the HTTP response.
        @rtype: L{Response}
        """

        store = self.storeMap[self.server.details()]
        j = json.loads(self.data)
        if self.stream is not None:
            j["stream"] = self.stream
            j["streamType"] = self.streamType
        try:
            if store.conduit.isStreamAction(j):
                stream = ProducerStream()

                class StreamProtocol(Protocol):
                    def connectionMade(self):
                        stream.registerProducer(self.transport, False)

                    def dataReceived(self, data):
                        stream.write(data)

                    def connectionLost(self, reason):
                        stream.finish()

                result = yield store.conduit.processRequestStream(
                    j, StreamProtocol())

                try:
                    ct, name = result
                except ValueError:
                    code = responsecode.BAD_REQUEST
                else:
                    headers = {"content-type": MimeType.fromString(ct)}
                    headers["content-disposition"] = MimeDisposition(
                        "attachment", params={"filename": name})
                    returnValue(Response(responsecode.OK, headers, stream))
            else:
                result = yield store.conduit.processRequest(j)
                code = responsecode.OK
        except Exception as e:
            # Send the exception over to the other side
            result = {
                "result": "exception",
                "class": ".".join((
                    e.__class__.__module__,
                    e.__class__.__name__,
                )),
                "details": str(e),
            }
            code = responsecode.BAD_REQUEST

        response = JSONResponse(code, result)
        returnValue(response)
    def actionCapabilities(self, request):
        """
        Return the capabilities of this server.
        """

        if len(request.args) != 0:
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        urlbase = request.path.rsplit("/", 1)[0]
        result = {
            "version": "1",
            "info": {
                "primary-source" if self.primary else "secondary_source": self.info_source,
                "formats": self.formats,
                "contacts": [],
            },
            "actions": [
                {
                    "name": "capabilities",
                    "uri-template": joinURL(urlbase, "capabilities"),
                    "parameters": [],
                },
                {
                    "name": "list",
                    "uri-template": joinURL(urlbase, "zones{?changedsince}"),
                    "parameters": [
                        {"name": "changedsince", "required": False, "multi": False, },
                    ],
                },
                {
                    "name": "get",
                    "uri-template": joinURL(urlbase, "zones{/tzid}{?start,end}"),
                    "parameters": [
                        {"name": "start", "required": False, "multi": False},
                        {"name": "stop", "required": False, "multi": False, },
                    ],
                },
                {
                    "name": "expand",
                    "uri-template": joinURL(urlbase, "zones{/tzid}/observances{?start,end}"),
                    "parameters": [
                        {"name": "start", "required": True, "multi": False, },
                        {"name": "end", "required": True, "multi": False, },
                    ],
                },
                {
                    "name": "find",
                    "uri-template": joinURL(urlbase, "zones{?pattern}"),
                    "parameters": [
                        {"name": "pattern", "required": True, "multi": False, },
                    ],
                },
            ]
        }
        return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
예제 #7
0
 def problemReport(self, code, description, status):
     raise HTTPError(JSONResponse(
         status,
         {
             "type": "https://datatracker.ietf.org/doc/draft-ietf-tzdist-service/",
             "error-code": code,
             "title": description,
             "status": status,
         },
         contentType="application/problem+json",
         pretty=config.TimezoneService.PrettyPrintJSON,
     ))
예제 #8
0
    def http_POST(self, request):
        """
        The server-to-server POST method.
        """

        # Check shared secret
        if not self.store.directoryService().serversDB.getThisServer(
        ).checkSharedSecret(request.headers):
            self.log.error("Invalid shared secret header in cross-pod request")
            raise HTTPError(
                StatusResponse(responsecode.FORBIDDEN,
                               "Not authorized to make this request"))

        # Look for XPOD header
        xpod = request.headers.getRawHeaders("XPOD")
        contentType = request.headers.getHeader("content-type")
        if xpod is not None:
            # Attachments are sent in the request body with the JSON data in a header. We
            # decode the header and add the request.stream as an attribute of the JSON object.
            xpod = xpod[0]
            try:
                j = json.loads(base64.b64decode(xpod))
            except (TypeError, ValueError) as e:
                self.log.error("Invalid JSON header in request: {ex}\n{xpod}",
                               ex=e,
                               xpod=xpod)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "Invalid JSON header in request: {}\n{}".format(
                            e, xpod)))
            j["stream"] = request.stream
            j["streamType"] = contentType
        else:
            # Check content first
            if "{}/{}".format(contentType.mediaType,
                              contentType.mediaSubtype) != "application/json":
                self.log.error("MIME type {mime} not allowed in request",
                               mime=contentType)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "MIME type {} not allowed in request".format(
                            contentType)))

            body = (yield allDataFromStream(request.stream))
            try:
                j = json.loads(body)
            except ValueError as e:
                self.log.error("Invalid JSON data in request: {ex}\n{body}",
                               ex=e,
                               body=body)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "Invalid JSON data in request: {}\n{}".format(e,
                                                                      body)))

        # Log extended item
        if not hasattr(request, "extendedLogItems"):
            request.extendedLogItems = {}
        request.extendedLogItems[
            "xpod"] = j["action"] if "action" in j else "unknown"

        # Get the conduit to process the data
        try:
            result = yield self.store.conduit.processRequest(j)
        except FailedCrossPodRequestError as e:
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
        except Exception as e:
            raise HTTPError(
                StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e)))

        response = JSONResponse(responsecode.OK, result)
        returnValue(response)
예제 #9
0
    def actionFind(self, request):
        """
        Return a list of all timezones matching a pattern.
        """

        if set(request.args.keys()) - set(("pattern",)):
            self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST)

        pattern = request.args.get("pattern", ())
        if len(pattern) == 0:
            self.problemReport("invalid-pattern", "Missing pattern request-URI query parameter", responsecode.BAD_REQUEST)
        elif len(pattern) > 1:
            self.problemReport("invalid-pattern", "Too many pattern request-URI query parameters", responsecode.BAD_REQUEST)
        pattern = pattern[0]

        def _comp_is(pattern, s):
            return pattern == s
        def _comp_startswith(pattern, s):
            return s.startswith(pattern)
        def _comp_endswith(pattern, s):
            return s.endswith(pattern)
        def _comp_contains(pattern, s):
            return pattern in s

        def _normalize(s):
            return s.replace("_", " ").lower()

        if pattern.startswith("*") and pattern.endswith("*"):
            pattern = pattern[1:-1]
            comparator = _comp_contains
        elif pattern.endswith("*"):
            pattern = pattern[:-1]
            comparator = _comp_startswith
        elif pattern.startswith("*"):
            pattern = pattern[1:]
            comparator = _comp_endswith
        else:
            comparator = _comp_is
        pattern = _normalize(pattern)

        if not pattern:
            self.problemReport("invalid-pattern", "Invalid pattern request-URI query parameter value", responsecode.BAD_REQUEST)

        timezones = []
        for tz in self.timezones.listTimezones(None):
            matched = comparator(pattern, _normalize(tz.tzid))
            if not matched:
                for alias in tz.aliases:
                    if comparator(pattern, _normalize(alias)):
                        matched = True
                        break
            if matched:
                timezones.append({
                    "tzid": tz.tzid,
                    "last-modified": tz.dtstamp,
                    "aliases": tz.aliases,
                })

        result = {
            "dtstamp": self.timezones.dtstamp,
            "timezones": timezones,
        }
        return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
예제 #10
0
    def http_POST(self, request):
        """
        The server-to-server POST method.
        """

        # Check shared secret
        if not self.store.directoryService().serversDB().getThisServer(
        ).checkSharedSecret(request.headers):
            self.log.error("Invalid shared secret header in cross-pod request")
            raise HTTPError(
                StatusResponse(responsecode.FORBIDDEN,
                               "Not authorized to make this request"))

        # Look for XPOD header
        xpod = request.headers.getRawHeaders("XPOD")
        contentType = request.headers.getHeader("content-type")
        if xpod is not None:
            # Attachments are sent in the request body with the JSON data in a header. We
            # decode the header and add the request.stream as an attribute of the JSON object.
            xpod = xpod[0]
            try:
                j = json.loads(base64.b64decode(xpod))
            except (TypeError, ValueError) as e:
                self.log.error("Invalid JSON header in request: {ex}\n{xpod}",
                               ex=e,
                               xpod=xpod)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "Invalid JSON header in request: {}\n{}".format(
                            e, xpod)))
            j["stream"] = request.stream
            j["streamType"] = contentType
        else:
            # Check content first
            if "{}/{}".format(contentType.mediaType,
                              contentType.mediaSubtype) != "application/json":
                self.log.error("MIME type {mime} not allowed in request",
                               mime=contentType)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "MIME type {} not allowed in request".format(
                            contentType)))

            body = (yield allDataFromStream(request.stream))
            try:
                j = json.loads(body)
            except ValueError as e:
                self.log.error("Invalid JSON data in request: {ex}\n{body}",
                               ex=e,
                               body=body)
                raise HTTPError(
                    StatusResponse(
                        responsecode.BAD_REQUEST,
                        "Invalid JSON data in request: {}\n{}".format(e,
                                                                      body)))

        # Log extended item
        if not hasattr(request, "extendedLogItems"):
            request.extendedLogItems = {}
        request.extendedLogItems[
            "xpod"] = j["action"] if "action" in j else "unknown"

        # Look for a streaming action which needs special handling
        if self.store.conduit.isStreamAction(j):
            # Get the conduit to process the data stream
            try:

                stream = ProducerStream()

                class StreamProtocol(Protocol):
                    def connectionMade(self):
                        stream.registerProducer(self.transport, False)

                    def dataReceived(self, data):
                        stream.write(data)

                    def connectionLost(self, reason):
                        stream.finish()

                result = yield self.store.conduit.processRequestStream(
                    j, StreamProtocol())

                try:
                    ct, name = result
                except ValueError:
                    code = responsecode.BAD_REQUEST
                else:
                    headers = {"content-type": MimeType.fromString(ct)}
                    headers["content-disposition"] = MimeDisposition(
                        "attachment", params={"filename": name})
                    returnValue(Response(responsecode.OK, headers, stream))

            except Exception as e:
                # Send the exception over to the other side
                result = {
                    "result":
                    "exception",
                    "class":
                    ".".join((
                        e.__class__.__module__,
                        e.__class__.__name__,
                    )),
                    "details":
                    str(e),
                }
                code = responsecode.BAD_REQUEST

        else:
            # Get the conduit to process the data
            try:
                result = yield self.store.conduit.processRequest(j)
                code = responsecode.OK if result[
                    "result"] == "ok" else responsecode.BAD_REQUEST
            except Exception as e:
                # Send the exception over to the other side
                result = {
                    "result":
                    "exception",
                    "class":
                    ".".join((
                        e.__class__.__module__,
                        e.__class__.__name__,
                    )),
                    "details":
                    str(e),
                }
                code = responsecode.BAD_REQUEST

        response = JSONResponse(code, result)
        returnValue(response)